<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
	<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
	<title>Shambletown</title>
<script type="text/javascript">
// From File src/host/html5.js
/** Host interface for html5 web browsers.
*/
var host = {
	/** The width of a character in pixels. */
	_cellWidth: 16,
	/** The height of a character in pixels. */
	_cellHeight: 16,
	/** Font family. */
	_fontFamily: "sans-serif",
	/** Font weight. */
	_fontWeight: "bold",
	/** The DOM Node of the base screen div. */
	_screenDiv: null,
	/** Array of screen cell div DOM nodes. */
	_cells: null,
	/** Width of the screen in cells. */
	_width: 0,
	/** Height of the screen in cells. */
	_height: 0,
	/** Input handling flag. */
	_nonChar: false,
	/** Input action consumer method. */
	inputConsumer: null,
	/** The screen buffer. */
	screen: null,
	/** Initialize the input and display.
	@param width Width of the display in cells.
	@param height Height of the display in cells.
	@param inputConsumer Input consumer (may be null).
	*/
	init: function(width, height, inputConsumer)
	{
		// TODO Implement Chrome Frame support
		this.inputConsumer = inputConsumer;
		this._setUpDisplay(width, height);
		document.onkeydown = function(e)
		{
			return host.keyDown(e);
		}
		document.onkeypress = function(e)
		{
			return host.keyDown(e);
		}

		// Special "active" screen buffer object
		function ActiveScreenBuffer() {}
		ActiveScreenBuffer.prototype = new ScreenBuffer(width, height);
		ActiveScreenBuffer.prototype.setBufferCell = ActiveScreenBuffer.prototype.setCell;
		ActiveScreenBuffer.prototype.setCell = this._setScreenCell;
		this.screen = new ActiveScreenBuffer();
		this.screen.width = width;
		this.screen.height = height;
	},
	/** Keyboard input handling.
	@param e The event object.
	*/
	keyDown: function(e)
	{
		var c, action = "none";
		var evt = (e) ? e : window.event;
		
		if(evt.type == "keydown")
		{
			c = evt.keyCode;
			if(c <16 ||
				(c> 16 && c <32) ||
				(c> 32 && c <41) ||
				c == 46)
			{
				this._nonChar = true;
				
				// Handle non-character keys
				switch(c)
				{
					case 13: action = "use"; break;
					case 27: action = "cancel"; break;
					case 33: action = "logup"; break;
					case 34: action = "logdown"; break;
					case 35: action = "historydown"; break;
					case 36: action = "historyup"; break;
					case 37: action = "left"; break;
					case 38: action = "up"; break;
					case 39: action = "right"; break;
					case 40: action = "down"; break;
					default:
						//alert(c);
						break;
				}
				if(action != "none")
					this.dispatchInputAction(action);

				try
				{
					if(Event.stop)
						Event.stop(evt);
				}
				catch(exp){}
				evt.returnValue = false;
			}
			else
				this._nonChar = false;
		}
		else
		{
			if(this._nonChar)
				return true;
			c = (evt.charCode) ? evt.charCode : evt.keyCode;
			if(c> 31 && c < 256)
			{
				switch(c)
				{
					case 32: action = "menu"; break;
					case 43: action = "cancel"; break;
					case 47: action = "help"; break;
					case 48: action = "menu"; break;
					case 50: action = "down"; break;
					case 51: action = "look"; break;
					case 52: action = "left"; break;
					case 54: action = "right"; break;
					case 56: action = "up"; break;
					case 63: action = "help"; break;
					case 104: action = "help"; break;
					case 108: action = "look"; break;
					default:
						//alert(c);
						break;
				}
				if(action != "none")
					this.dispatchInputAction(action);

				try
				{
					if(Event.stop)
						Event.stop(evt);
				}
				catch(exp){}
				evt.returnValue = false;
			}
		}
		return evt.returnValue;
	},
	/** Dispatches a single input action.
	@param action Name of the action.
	*/
	dispatchInputAction: function(action)
	{
		if(this.inputConsumer !== null)
			this.inputConsumer(action);
	},
	/** Test is saving is available.
	 */
	isSaveAvailable: function()
	{
		if(window.localStorage)
			return true;
		return false;
	},
	/** Test if a value is available.
	 *@param key THe key name of the value.
	 */
	isValueAvailable: function(key)
	{
		if(!window.localStorage)
			throw new Error("Save feature is not available in this browser.");
		if(window.localStorage[key] !== undefined &&
			window.localStorage[key].length > 0)
			return true;
		return false;
	},
	/** Save a key-value pair.
	 * @param key The key name.
	 * @param value The value to store.
	 */
	saveData: function(key, value)
	{
		if(!window.localStorage)
			throw new Error("Save feature is not available in this browser.");
		window.localStorage[key] = value;
	},
	/** Loads a key-value pair.
	 *@param key The key name.
	 */
	loadData: function(key)
	{
		if(!window.localStorage)
			throw new Error("Save feature is not available in this browser.");
		return window.localStorage[key];
	},
	/** Delete a key-value pair.
	 *@param key The key name.
	 */
	deleteData: function(key)
	{
		if(!window.localStorage)
			throw new Error("Save feature is not available in this browser.");
		window.localStorage[key] = "";
		delete(window.localStorage[key]);
	},
	/** Set a screen cell.
	@param x X position.
	@param y Y position.
	@param c Character to put into the cell.
	@param bg Background color of the cell.
	@param fg Foreground color of the cell.
	*/
	_setScreenCell: function(x, y, c, fg, bg)
	{
		this.setBufferCell(x, y, c, fg, bg);
		if(x < 0 ||
			x >= host._width ||
			y < 0 ||
			y >= host._height)
			return;
		var cellDiv = host._cells[y * host._width + x];
		if(bg !== null)
			cellDiv.style.backgroundColor = bg.cssColorString();
		if(fg !== null)
			cellDiv.style.color = fg.cssColorString();
		if(c !== null)
			// Fix for IE
			cellDiv.firstChild.innerHTML = c == "<" ? "&lt;" : c;
	},
	/** (re)Initialize the display.
	@param width Width of the display in cells.
	@param height Height of the display in cells.
	*/
	_setUpDisplay: function(width, height)
	{
		this._removeChildren(document.body);
		this._width = width;
		this._height = height;
		this._screenDiv = document.createElement("div");
		this._screenDiv.style.width = (width * this._cellWidth) + "px";
		this._screenDiv.style.height = (height * this._cellHeight) + "px";
		this._screenDiv.style.position = "relative";
		this._screenDiv.style.padding = "0";
		this._screenDiv.style.margin = "0";
		this._screenDiv.style.fontFamily = this._fontFamily;
		this._screenDiv.style.fontSize = this._cellHeight + "px";
		this._screenDiv.style.border = "none";
		this._cells = [];
		for(var y = 0; y < height; ++y)
		{
			for(var x = 0; x < width; ++x)
			{
				var cellDiv = document.createElement("div");
				cellDiv.style.width = (this._cellWidth + 1) + "px";
				cellDiv.style.height = (this._cellHeight + 1) + "px";
				cellDiv.style.textAlign = "center";
				cellDiv.style.backgroundColor = "#000000";
				cellDiv.style.position = "absolute";
				cellDiv.style.top = (y * this._cellHeight) + "px";
				cellDiv.style.left = (x * this._cellWidth) + "px";
				cellDiv.style.padding = "0";
				cellDiv.style.margin = "0";
				cellDiv.style.border = "none";
				var inner = document.createElement("div");
				inner.style.position = "absolute";
				inner.style.top = "-2px";
				inner.style.left = "0px";
				inner.style.padding = "0";
				inner.style.margin = "0";
				inner.style.textAlign = "center";
				inner.style.width = this._cellWidth + "px";
				inner.style.height = this._cellHeight + "px";
				inner.style.border = "none";
				inner.style.fontWeight = this._fontWeight;
				cellDiv.appendChild(inner);
				this._cells[y * this._width + x] = cellDiv;
				this._screenDiv.appendChild(cellDiv);
			}
		}
		document.body.appendChild(this._screenDiv);
	},
	/** Remove all children from a DOM Node.
	@param node Node to remove children from.
	*/
	_removeChildren: function(node)
	{
		while(node.hasChildNodes())
		{
			node.removeChild(node.lastChild);
		}
	}
};
</script>
<script type="text/javascript">
// From File src/util.js
var util =
{
	letters: "abcdefghijklmnopqrstuvwxyz",
	wordWrap: function(str, width)
	{
		var regex = '.{1,' + width + '}(\\s|$)|.{' + width + '}|.+$';
		return str.match(RegExp(regex, 'g'));
	},
	bytesToKb: function(bytes)
	{
		return Math.floor((bytes / 1024) * 10) / 10;
	}
};

// Extension methods
if(!Array.indexOf)
{
	Array.prototype.indexOf = function(obj)
	{
		for(var i = 0; i < this.length; ++i)
		{
			if(this[i] == obj)
				return i;
		}
		return -1;
	}
}

/** Repeatable Random Number Generator.
@param seed The initial seed value (optional).
*/
function Rrng(seed)
{
	if(seed !== undefined)
		this._last = seed;
	else
		this._last = 59051;
}
Rrng.prototype =
{
	/** Gets the next random number in the sequence.
	@param p1 Inclusive minimum (optional).
	@param p2 Exclusive maximum (optional).
	*/
	next: function(p1, p2)
	{
		this._last = (251 * this._last + 32749) % 65521;
		if(p1 != undefined)
		{
			if(p2 != undefined)
				if(p2 == p1)
					return p1;
				else
					return (this._last % (p2 - p1)) + p1;
			else
				return this._last % p1;
		}
		return this._last;
	},
	/** Gets the next float in the sequence in the range 0 to 1 inclusive.
	
	The next float is calculated by getting the next integer from 0 to 10000
	and dividing that by 10000.
	*/
	nextFloat: function()
	{
		return this.next(0, 10001) / 10000;
	},
	/** Gets a random element from an array.
	
	The array element is calculated by getting the integer from 0 to
	array.length and indexing the array by this value.
	*/
	nextElement: function(array)
	{
		return array[this.next(0, array.length)];
	},
	/** Sets the seed.
	@param seed The new seed value.
	*/
	seed: function(seed)
	{
		this._last = seed;
	}
}

/** Global Rrng object which MUST NOT BE USED for generation as it will change
 *on every load.
 */
var rand = new Rrng(Math.floor(Math.random() * 65521));
</script>
<script type="text/javascript">
// From File src/json2.js
/*
    http://www.JSON.org/json2.js
    2010-11-17

    Public Domain.

    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

    See http://www.JSON.org/js.html


    This code should be minified before deployment.
    See http://javascript.crockford.com/jsmin.html

    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
    NOT CONTROL.


    This file creates a global JSON object containing two methods: stringify
    and parse.

        JSON.stringify(value, replacer, space)
            value       any JavaScript value, usually an object or array.

            replacer    an optional parameter that determines how object
                        values are stringified for objects. It can be a
                        function or an array of strings.

            space       an optional parameter that specifies the indentation
                        of nested structures. If it is omitted, the text will
                        be packed without extra whitespace. If it is a number,
                        it will specify the number of spaces to indent at each
                        level. If it is a string (such as '\t' or '&nbsp;'),
                        it contains the characters used to indent at each level.

            This method produces a JSON text from a JavaScript value.

            When an object value is found, if the object contains a toJSON
            method, its toJSON method will be called and the result will be
            stringified. A toJSON method does not serialize: it returns the
            value represented by the name/value pair that should be serialized,
            or undefined if nothing should be serialized. The toJSON method
            will be passed the key associated with the value, and this will be
            bound to the value

            For example, this would serialize Dates as ISO strings.

                Date.prototype.toJSON = function (key) {
                    function f(n) {
                        // Format integers to have at least two digits.
                        return n < 10 ? '0' + n : n;
                    }

                    return this.getUTCFullYear()   + '-' +
                         f(this.getUTCMonth() + 1) + '-' +
                         f(this.getUTCDate())      + 'T' +
                         f(this.getUTCHours())     + ':' +
                         f(this.getUTCMinutes())   + ':' +
                         f(this.getUTCSeconds())   + 'Z';
                };

            You can provide an optional replacer method. It will be passed the
            key and value of each member, with this bound to the containing
            object. The value that is returned from your method will be
            serialized. If your method returns undefined, then the member will
            be excluded from the serialization.

            If the replacer parameter is an array of strings, then it will be
            used to select the members to be serialized. It filters the results
            such that only members with keys listed in the replacer array are
            stringified.

            Values that do not have JSON representations, such as undefined or
            functions, will not be serialized. Such values in objects will be
            dropped; in arrays they will be replaced with null. You can use
            a replacer function to replace those with JSON values.
            JSON.stringify(undefined) returns undefined.

            The optional space parameter produces a stringification of the
            value that is filled with line breaks and indentation to make it
            easier to read.

            If the space parameter is a non-empty string, then that string will
            be used for indentation. If the space parameter is a number, then
            the indentation will be that many spaces.

            Example:

            text = JSON.stringify(['e', {pluribus: 'unum'}]);
            // text is '["e",{"pluribus":"unum"}]'


            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

            text = JSON.stringify([new Date()], function (key, value) {
                return this[key] instanceof Date ?
                    'Date(' + this[key] + ')' : value;
            });
            // text is '["Date(---current time---)"]'


        JSON.parse(text, reviver)
            This method parses a JSON text to produce an object or array.
            It can throw a SyntaxError exception.

            The optional reviver parameter is a function that can filter and
            transform the results. It receives each of the keys and values,
            and its return value is used instead of the original value.
            If it returns what it received, then the structure is not modified.
            If it returns undefined then the member is deleted.

            Example:

            // Parse the text. Values that look like ISO date strings will
            // be converted to Date objects.

            myData = JSON.parse(text, function (key, value) {
                var a;
                if (typeof value === 'string') {
                    a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
                    if (a) {
                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
                            +a[5], +a[6]));
                    }
                }
                return value;
            });

            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
                var d;
                if (typeof value === 'string' &&
                        value.slice(0, 5) === 'Date(' &&
                        value.slice(-1) === ')') {
                    d = new Date(value.slice(5, -1));
                    if (d) {
                        return d;
                    }
                }
                return value;
            });


    This is a reference implementation. You are free to copy, modify, or
    redistribute.
*/

/*jslint evil: true, strict: false, regexp: false */

/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
    lastIndex, length, parse, prototype, push, replace, slice, stringify,
    test, toJSON, toString, valueOf
*/


// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.

if (!this.JSON) {
    this.JSON = {};
}

(function () {
    "use strict";

    function f(n) {
        // Format integers to have at least two digits.
        return n < 10 ? '0' + n : n;
    }

    if (typeof Date.prototype.toJSON !== 'function') {

        Date.prototype.toJSON = function (key) {

            return isFinite(this.valueOf()) ?
                   this.getUTCFullYear()   + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate())      + 'T' +
                 f(this.getUTCHours())     + ':' +
                 f(this.getUTCMinutes())   + ':' +
                 f(this.getUTCSeconds())   + 'Z' : null;
        };

        String.prototype.toJSON =
        Number.prototype.toJSON =
        Boolean.prototype.toJSON = function (key) {
            return this.valueOf();
        };
    }

    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        gap,
        indent,
        meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        rep;


    function quote(string) {

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.

        escapable.lastIndex = 0;
        return escapable.test(string) ?
            '"' + string.replace(escapable, function (a) {
                var c = meta[a];
                return typeof c === 'string' ? c :
                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
            }) + '"' :
            '"' + string + '"';
    }


    function str(key, holder) {

// Produce a string from holder[key].

        var i,          // The loop counter.
            k,          // The member key.
            v,          // The member value.
            length,
            mind = gap,
            partial,
            value = holder[key];

// If the value has a toJSON method, call it to obtain a replacement value.

        if (value && typeof value === 'object' &&
                typeof value.toJSON === 'function') {
            value = value.toJSON(key);
        }

// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.

        if (typeof rep === 'function') {
            value = rep.call(holder, key, value);
        }

// What happens next depends on the value's type.

        switch (typeof value) {
        case 'string':
            return quote(value);

        case 'number':

// JSON numbers must be finite. Encode non-finite numbers as null.

            return isFinite(value) ? String(value) : 'null';

        case 'boolean':
        case 'null':

// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.

            return String(value);

// If the type is 'object', we might be dealing with an object or an array or
// null.

        case 'object':

// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.

            if (!value) {
                return 'null';
            }

// Make an array to hold the partial results of stringifying this object value.

            gap += indent;
            partial = [];

// Is the value an array?

            if (Object.prototype.toString.apply(value) === '[object Array]') {

// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

                length = value.length;
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value) || 'null';
                }

// Join all of the elements together, separated with commas, and wrap them in
// brackets.

                v = partial.length === 0 ? '[]' :
                    gap ? '[\n' + gap +
                            partial.join(',\n' + gap) + '\n' +
                                mind + ']' :
                          '[' + partial.join(',') + ']';
                gap = mind;
                return v;
            }

// If the replacer is an array, use it to select the members to be stringified.

            if (rep && typeof rep === 'object') {
                length = rep.length;
                for (i = 0; i < length; i += 1) {
                    k = rep[i];
                    if (typeof k === 'string') {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            } else {

// Otherwise, iterate through all of the keys in the object.

                for (k in value) {
                    if (Object.hasOwnProperty.call(value, k)) {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            }

// Join all of the member texts together, separated with commas,
// and wrap them in braces.

            v = partial.length === 0 ? '{}' :
                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                        mind + '}' : '{' + partial.join(',') + '}';
            gap = mind;
            return v;
        }
    }

// If the JSON object does not yet have a stringify method, give it one.

    if (typeof JSON.stringify !== 'function') {
        JSON.stringify = function (value, replacer, space) {

// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.

            var i;
            gap = '';
            indent = '';

// If the space parameter is a number, make an indent string containing that
// many spaces.

            if (typeof space === 'number') {
                for (i = 0; i < space; i += 1) {
                    indent += ' ';
                }

// If the space parameter is a string, it will be used as the indent string.

            } else if (typeof space === 'string') {
                indent = space;
            }

// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.

            rep = replacer;
            if (replacer && typeof replacer !== 'function' &&
                    (typeof replacer !== 'object' ||
                     typeof replacer.length !== 'number')) {
                throw new Error('JSON.stringify');
            }

// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.

            return str('', {'': value});
        };
    }


// If the JSON object does not yet have a parse method, give it one.

    if (typeof JSON.parse !== 'function') {
        JSON.parse = function (text, reviver) {

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.

            var j;

            function walk(holder, key) {

// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.

                var k, v, value = holder[key];
                if (value && typeof value === 'object') {
                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = walk(value, k);
                            if (v !== undefined) {
                                value[k] = v;
                            } else {
                                delete value[k];
                            }
                        }
                    }
                }
                return reviver.call(holder, key, value);
            }


// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.

            text = String(text);
            cx.lastIndex = 0;
            if (cx.test(text)) {
                text = text.replace(cx, function (a) {
                    return '\\u' +
                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
                });
            }

// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.

// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

            if (/^[\],:{}\s]*$/
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                j = eval('(' + text + ')');

// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.

                return typeof reviver === 'function' ?
                    walk({'': j}, '') : j;
            }

// If the text is not JSON parseable, then a SyntaxError is thrown.

            throw new SyntaxError('JSON.parse');
        };
    }
}());
</script>
<script type="text/javascript">
// From File src/main.js
var version = "0.03";
var logWindow = null;
var worldMap = null;
var worldMapMode = null;
var titleMode = null;
var modeStack = [];
var player = null;
var frame = 0;
var inGameMode = false;

function main()
{
	host.init(60, 36, onInputAction);
	enterTitleMode();
}
function onInputAction(action)
{
	// Let the log window get the first crack at it.
	if(logWindow &&
		logWindow.handleAction(action))
		return;
	for(var i = modeStack.length - 1; i >= 0; --i)
	{
		if(!modeStack[i].handleAction)
			continue;
		if(modeStack[i].handleAction(action))
			break;
	}
}
function removeMode(mode)
{
	var idx = modeStack.indexOf(mode);
	if(idx >= 0)
		modeStack.splice(idx, 1);
	for(var i = 0; i < modeStack.length; ++i)
	{
		if(modeStack[i].render)
			modeStack[i].render();
	}
	if(logWindow)
		logWindow.render();
}
function enterTitleMode()
{
	leaveGameMode();
	leaveTitleMode();
	titleMode = new TitleScreenMode();
	modeStack.push(titleMode);
	titleMode.createMainMenu();
}
function leaveTitleMode()
{
	removeMode(titleMode);
}
function enterGameMode()
{
	leaveTitleMode();
	leaveGameMode();
	logWindow = new LogWindow(host.screen, 0, 32, 60, 4, 128);
	worldMapMode = new WorldMapMode(worldMap);
	modeStack.push(worldMapMode);
}
function leaveGameMode()
{
	logWindow = null;
	removeMode(worldMapMode);
}
function saveGame()
{
	// See if saving works
	if(!host.isSaveAvailable())
	{
		logWindow.pushMessage("%90Saving is not supported.");
		return false;
	}

	var saveData;

	try
	{
		// Build the save data
		var worldMapData = [];
		worldMap.serialize(worldMapData);
		var save =
		{
			version: version,
			seed: worldMap.seed,
			player: Entity.getSerializedForm(player),
			worldMap: worldMapData,
			sectorMaps: SectorMap.getSaveData()
		}
		saveData = JSON.stringify(save);
		host.saveData("save", saveData);
	}
	catch(exp)
	{
		reportCriticalError("Saving game failed: " + exp.toString());
		return false;
	}

	logWindow.pushMessage("%a0Saved game " + save.seed + " successfully (" + util.bytesToKb(saveData.length) + "kb).");
	return true;
}
function isSaveAvailable()
{
	// See if saving works
	if(!host.isSaveAvailable())
		return false;
	return host.isValueAvailable("save");
}
function loadGame()
{
	// See if saving works
	if(!host.isSaveAvailable())
	{
		reportCriticalError("Saving is not supported.");
		return false;
	}

	var saveData = null;

	try
	{
		saveData = host.loadData("save");
		if(!saveData)
			return false;
		var save = JSON.parse(saveData);
		if(save.version !== version)
		{
			reportCriticalError("The saved game present is for version " + save.version + " and is not compatible.");
			return false;
		}

		// Load proceedure
		player = Entity.fromSerializedData(save.player);
		SectorMap.unregisterAllMaps();
		worldMap = new WorldMap(host.screen, 0, 0, 40, 32, save.seed, player);
		worldMap.deserialize(save.worldMap);
		SectorMap.loadSaveData(save.sectorMaps);
	}
	catch(exp)
	{
		reportCriticalError("Loading game failed: " + exp.toString());
		return false;
	}

	leaveTitleMode();
	enterGameMode();
	logWindow.pushMessage("%a0Game " + save.seed + " loaded successfully (" + util.bytesToKb(saveData.length) + "kb).");
	return true;
}
function newGame()
{
	deleteSave();
	player = new PlayerMobile("Playa", "@", Color.white, Color.black, 20);
	SectorMap.unregisterAllMaps();
	var seed = Math.floor(Math.random() * 10000);
	//seed = 6811;
	worldMap = new WorldMap(host.screen, 0, 0, 40, 32, seed, player);
	leaveTitleMode();
	enterGameMode();
	logWindow.pushMessage("%a0Started new game " + seed + ".");
	textBox("General Help");
}
function deleteSave()
{
	try
	{
		host.deleteData("save");
	}
	catch(exp)
	{
	}
}
/** Utility method for displaying a message box.
 *@param text The text to display. Do not include newlines.
 */
function messageBox(text)
{
	modeStack.push(
		new MenuListMode(
			host.screen,
			40,
			text,
			null,
			null
		)
	);
}
/** Standard critical error reporting method.
 *@param error A description of the error.
 */
function reportCriticalError(error)
{
	messageBox("%90" + error);
}
/** Quickly pull up a standard text box.
 *@param name Name of the text resource to display in the text box.
 */
function textBox(name)
{
	modeStack.push(
		new TextReaderMode(
			host.screen,
			"%f0" + text[name],
			null,
			null
		)
	);
}
</script>
<script type="text/javascript">
// From File src/Screen.js
/** Screen management and output classes.
*/

/** Represents a screen color.
@param r Red intensity, 0 - 1.
@param g Green intensity, 0 - 1.
@param b Blue intentisy, 0 - 1.
*/
/** Copy constructor form.
@param r Color to copy from.
*/
function Color(r, g, b)
{
	if(g === undefined)
	{
		this.r = r.r;
		this.g = r.g;
		this.b = r.b;
	}
	else
	{
		this.r = r;
		this.g = g;
		this.b = b;
	}
}
Color.prototype =
{

	/** Gets the CSS rgb() function string for this color.
	*/
	cssColorString: function()
	{
		return "rgb(" + Math.min(Math.max(Math.floor(this.r * 255), 0), 255) +
			", " + Math.min(Math.max(Math.floor(this.g * 255), 0), 255) +
			", " + Math.min(Math.max(Math.floor(this.b * 255), 0), 255) + ")";
	},
	/** Creates a new color that is this color alpha blended with another color.
	 *@param c Color to blend with.
	 *@param a Alpha value of c.
	 */
	alphaBlend: function(c, a)
	{
		return new Color(
			(1 - a) * this.r + a * c.r,
			(1 - a) * this.g + a * c.g,
			(1 - a) * this.b + a * c.b
		);
	},
	/** Creates a new color that is this color multiplied by some value.
	 *@param value Value to multiply by.
	 */
	newScaled: function(value)
	{
		return new Color(
			this.r * value,
			this.g * value,
			this.b * value
		);
	}
};
/** Create a new color that is interpolated between two colors.
 *@param c1 The color to interpolate from.
 *@param c2 The color to interpolate to.
 *@param weight The weight of the interpolation, from 0 to 1.
 */
Color.interpolate = function(c1, c2, weight)
{
	var ret = new Color(c1);
	ret.r += (c2.r - ret.r) * weight;
	ret.g += (c2.g - ret.g) * weight;
	ret.b += (c2.b - ret.b) * weight;
	return ret;
}
// Color presets
Color.black = new Color(0, 0, 0);
Color.darkRed = new Color(0.66, 0, 0);
Color.darkGreen = new Color(0, 0.66, 0);
Color.brown = new Color(0.66, 0.33, 0);
Color.darkBlue = new Color(0, 0, 0.66);
Color.purple = new Color(0.66, 0, 0.66);
Color.teal = new Color(0, 0.66, 0.66);
Color.gray = new Color(0.66, 0.66, 0.66);
Color.grey = Color.gray;
Color.darkGray = new Color(0.33, 0.33, 0.33);
Color.darkGrey = Color.darkGray;
Color.red = new Color(1, 0, 0);
Color.green = new Color(0, 1, 0);
Color.yellow = new Color(1, 1, 0);
Color.blue = new Color(0, 0, 1);
Color.magenta = new Color(1, 0, 1);
Color.cyan = new Color(0, 1, 1);
Color.white = new Color(1, 1, 1);
// ASCII table of colors, yay!
Color.asciiCodes =
{
	'0': Color.black,
	'1': Color.darkRed,
	'2': Color.darkGreen,
	'3': Color.brown,
	'4': Color.darkBlue,
	'5': Color.purple,
	'6': Color.teal,
	'7': Color.gray,
	'8': Color.darkGray,
	'9': Color.red,
	'a': Color.green,
	'b': Color.yellow,
	'c': Color.blue,
	'd': Color.magenta,
	'e': Color.cyan,
	'f': Color.white,
	'g': null,
	'A': Color.green,
	'B': Color.yellow,
	'C': Color.blue,
	'D': Color.magenta,
	'E': Color.cyan,
	'F': Color.white,
	'G': null
};

/** Represents a screen cell.
@param c Character to display.
@param fg Foreground color.
@param bg Background color.
*/
function ScreenCell(c, fg, bg)
{
	this.c = c;
	this.fg = fg;
	this.bg = bg;
	this.changed = true;
}
ScreenCell.prototype =
{
};

/** Represents a buffer for screen output.
@param width Width in cells.
@param height Height in cells.
*/
function ScreenBuffer(width, height)
{
	this.width = width;
	this.height = height;
	this._cells = [];
	for(var i = 0; i < width * height; ++i)
	{
		this._cells.push(new ScreenCell(" ", new Color(Color.white), new Color(Color.black)));
	}
	this._stringFgColor = Color.white;
	this._stringBgColor = Color.black;
}
ScreenBuffer.prototype =
{
	/** Sets attributes about a cell. All parameters are optional but must be
	replaced by null.
	@param x X position.
	@param y Y position.
	@param c Character.
	@param fg Foreground color.
	@param bg Background color.
	*/
	setCell: function(x, y, c, fg, bg)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return;
		var cell = this._cells[y * this.width + x];
		if(c !== null)
			cell.c = c;
		if(fg !== null)
			cell.fg = fg;
		if(bg !== null)
			cell.bg = bg;
		cell.changed = true;
	},
	/** Gets the cell at the specified location, or null if out of range.
	 *@param x X position;
	 *@param y Y position;
	 */
	getCell: function(x, y)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return null;
		return this._cells[y * this.width + x];
	},
	/** Draws a string of ASCII text to the buffer, ignoring any character
	outside the range 32-127.
	

	Special text color escapements are supported in the form of "%XX", where X
	is a hex digit of one of the standard ASCII color values (see
	Color.asciiCodes). The first X sets the foreground color, the second the
	background color. Use "%%" for a percent sign.
	
	@param x X location.
	@param y Y location.
	@param str The string to blit.
	@param maxWidth The maximum width of the string to actually blit (optional). If specified this will clear to the end of the width.
	*/
	blitString: function(x, y, str, maxWidth)
	{
		var cx = 0, i, state = 0, c, code, temp;
		
		for(i = 0; i < str.length; ++i)
		{
			c = str.charAt(i);
			code = c.charCodeAt(0);
			if(code < 32 ||
				code > 127)
				continue;
			switch(state)
			{
				case 0:
					if(c == "%")
						state = 1;
					else
					{
						if(maxWidth === undefined ||
							cx < maxWidth)
							this.setCell(x + cx, y, c, this._stringFgColor, this._stringBgColor);
						cx++;
					}
					break;
				case 1:
					temp = Color.asciiCodes[c];
					if(temp !== undefined)
						this._stringFgColor = temp;
					state = 2;
					break;
				case 2:
					temp = Color.asciiCodes[c];
					if(temp !== undefined)
						this._stringBgColor = temp;
					state = 0;
					break;
			}
		}
		if(maxWidth !== undefined)
		{
			for(; cx < maxWidth; ++cx)
			{
				this.setCell(x + cx, y, " ", Color.white, Color.black);
			}
		}
	},
	/** Draws a rectangle with the specified attributes.
	@param x X position.
	@param y Y position.
	@param width Width.
	@param height Height.
	@param c Character.
	@param fg Foreground color.
	@param bg Background color.
	*/
	blitRect: function(x, y, width, height, c, fg, bg)
	{
		for(iy = y; iy < y + height; ++iy)
		{
			for(ix = x; ix < x + width; ++ix)
			{
				this.setCell(ix, iy, c, fg, bg);
			}
		}
	},
	/** Draws a box.
	@param x X position of box.
	@param y Y position of box.
	@param width Width of box.
	@param height Height of box.
	@param c Character to draw.
	@param fg Foreground color.
	@param bg Background color.
	*/
	drawBox: function(x, y, width, height, c, fg, bg)
	{
		// Horizontal lines
		this.blitRect(x, y, width, 1, c, fg, bg);
		this.blitRect(x, y + height - 1, width, 1, c, fg, bg);
		
		// Vertical lines
		this.blitRect(x, y + 1, 1, height - 2, c, fg, bg);
		this.blitRect(x + width - 1, y + 1, 1, height - 2, c, fg, bg);
	},
	/** Draws a menu box around )on the outside of) a given area.
	@param x X position of area.
	@param y Y position of area.
	@param width Width of area.
	@param height Height of area.
	*/
	drawMenuBox: function(x, y, width, height)
	{
		// Horizontal lines
		this.blitRect(x - 1, y - 1, width + 2, 1, " ", null, Color.white);
		this.blitRect(x - 1, y + height, width + 2, 1, " ", null, Color.white);
		
		// Vertical lines
		this.blitRect(x - 1, y, 1, height, " ", null, Color.white);
		this.blitRect(x + width, y, 1, height, " ", null, Color.white);
		
		// Drop shadown
		this.blitRect(x, y + height + 1, width + 2, 1, " ", null, Color.gray);
		this.blitRect(x + width + 1, y, 1, height + 1, " ", null, Color.gray);
	},
	/** Draws every cell in the buffer.
	@param c Character.
	@param fg Foreground color.
	@param bg Background color.
	*/
	clear: function(c, fg, bg)
	{
		this.blitRect(0, 0, this.width, this.height, c, fg, bg);
	},
	/** Copies an area of the buffer to another area. These areas may overlap.
	 *@param x X position of area to copy.
	 *@param y Y position of area to copy.
	 *@param width Width of area to copy.
	 *@param height Height of area to copy.
	 *@param dx Destination X position to copy to.
	 *@param dy Destination Y position to copy to.
	 */
	copyRectTo: function(x, y, width, height, dx, dy)
	{
		var ofs = 0;
		var buffer = [];
		var ix, iy, cell;
		for(iy = y; iy < y + height; ++y)
		{
			for(ix = x; ix < x + width; ++x)
			{
				cell = this._cells[iy * this.width + ix];
				buffer.push(new ScreenCell(cell.c, cell.fg, cell.bg));
			}
		}
		for(iy = y; iy < y + height; ++y)
		{
			for(ix = x; ix < x + width; ++x)
			{
				cell = buffer[ofs++];
				this.setCell(ix, iy, cell.c, cell.fg, cell.bg);
			}
		}
	},
	/** Pushes the contents of the buffer to another buffer
	@param dest Destination buffer.
	@param x X offset.
	@param y Y offset.
	@param force Blit even non-changed cells to the screen.
	*/
	blitTo: function(dest, x, y, force)
	{
		var ix, iy, ofs = 0, cell;
		
		for(iy = 0; iy < this.height; ++iy)
		{
			for(ix = 0; ix < this.width; ++ix)
			{
				cell = this._cells[ofs++];
				if(cell.changed ||
					force)
				{
					dest.setCell(ix + x, iy + y, cell.c, cell.fg, cell.bg);
					cell.changed = false;
				}
			}
		}
	}
};
</script>
<script type="text/javascript">
// From File src/WorldMap.js
/** Representation of a "sector" of the wasteland.
@param seed The Rrng seed associated with this sector.
*/
function WorldMapSector(seed)
{
	this.seed = seed;
	this.moisture = 0.15;
	this.heat = 1.1;
	this.recalculateColor();
	this.character = " ";
	this.name = "desert floor";
	this.blockWalk = false;
	this.destination = null;
	this.colorize = true;
	this.fg = Color.white;
}
WorldMapSector.prototype =
{
	/** Recalculates the sector's color based on it's heat and moisture
	values.
	*/
	recalculateColor: function()
	{
		if(!this.colorize)
			return;
		var mf = Math.min(Math.max(this.moisture, 0), 0.33);
		this.bg = new Color(
			0.66 * this.heat,
			(0.33 + (0.33 - mf)) * this.heat,
			0
		);
	}
};

/** The representation of our "world" map of the wasteland.
@param buffer Destination screen buffer.
@param xOfs Screen X offset.
@param yOfs Screen Y offset.
@param width Width in sectors.
@param height Height in sectors.
@param seed Rrng seed used.
@param player The player's mobile.
*/
function WorldMap(buffer, xOfs, yOfs, width, height, seed, player)
{
	this.buffer = buffer;
	this.xOfs = xOfs;
	this.yOfs = yOfs;
	this.width = width;
	this.height = height;
	this.seed = seed;
	this.player = player;
	this.playerX = 0;
	this.playerY = 0;
	this._sectors = [];
	this._knownMap = [];
	this.riverName = "Ambling River";

	// Initial map generation
	this.generateMap();
}
WorldMap.prototype =
{
	serialize: function(s)
	{
		s.push(this.playerX, this.playerY);

		// Save visibility information
		var visRle = [];
		var lastValue = 0;
		var count = 0;
		for(i = 0; i < this._knownMap.length; ++i)
		{
			if(this._knownMap[i] == lastValue)
				++count;
			else
			{
				visRle.push(count);
				lastValue = this._knownMap[i];
				count = 1;
			}
		}
		visRle.push(count);
		s.push(visRle);
	},
	deserialize: function(s)
	{
		// Load visibility information.
		var visRle = s.pop();
		var i, j, visible = false, ofs = 0;
		for(i = 0; i < visRle.length; ++i)
		{
			for(j = 0; j < visRle[i]; ++j)
			{
				this._knownMap[ofs++] = visible ? 1 : 0;
			}
			visible = !visible;
		}
		
		this.playerY = s.pop();
		this.playerX = s.pop();
	},
	/** Generates the world map's sector information for the seed value.
	*/
	generateMap: function()
	{
		var self = this;
		
		// Function that spreads heat / moisture from a point
		function spread(x, y, heatMod, moistureMod, radius)
		{
			var ix, iy, ds, rs = radius * radius, d, sector, dbyr;
			
			for(iy = y - radius; iy <= y + radius; ++iy)
			{
				if(iy < 0 ||
					iy >= self.height)
					continue;
				for(ix = x - radius; ix <= x + radius; ++ix)
				{
					if(ix < 0 ||
						ix >= self.width)
						continue;
					ds = (x - ix) * (x - ix) + (y - iy) * (y - iy);
					if(ds <= rs)
					{
						d = Math.sqrt(ds);
						dbyr = 1 - (d / radius);
						sector = self._sectors[iy * self.width + ix];
						sector.moisture += moistureMod * dbyr;
						sector.heat += heatMod * dbyr;
					}
				}
			}
		}
		
		// Function that (tries) to find a free location near a point
		function freeLocationNear(x, y, checkNearDest)
		{
			var i, tx, ty, sector, testSector, ix, iy, bad;
			
			for(i = 0; i < 5; ++i)
			{
				tx = x + rng.next(0, 3) - 1;
				ty = y + rng.next(0, 3) - 1;
				sector = self.getSector(tx, ty);
				// We have a valid sector, let's make sure it's not too close
				// to another destination.
				if(sector != null &&
					!sector.blockWalk &&
					sector.destination == null)
				{
					if(!checkNearDest)
						return sector;
					bad = false;
					for(iy = ty - 2; iy <= ty + 2; ++iy)
					{
						for(ix = tx - 2; ix <= tx + 2; ++ix)
						{
							testSector = self.getSector(ix, iy);
							if(testSector != null &&
								testSector.destination != null)
								bad = true;
						}
					}
					if(bad)
						continue;
					return sector;
				}
			}
			return null;
		}
		
		var rng = new Rrng(this.seed);
		
		// Create the sector objects, assign the seed values and initialized the
		// known map.
		for(var i = 0; i < this.width * this.height; ++i)
		{
			this._sectors.push(new WorldMapSector(rng.next()));
			this._sectors[i].id = i;
			this._knownMap.push(0);
		}
				
		// Heat map
		var count = rng.next(4, 8);
		for(var i = 0; i < count; ++i)
		{
			var cx = rng.next(0, this.width);
			var cy = rng.next(0, this.height);
			var size = rng.next(3, 20);
			var heatMod = (rng.nextFloat() * 0.3) - 0.15;
			var moistureMod = -(heatMod + .05);
			spread(cx, cy, heatMod, moistureMod, size);
		}
		
		// River
		var riverLocs = [];
		var rx = Math.floor(this.width / 2);
		var dir = 3;
		var ry = 0;
		while(ry < this.height + 5)
		{
			// Plot the river segment
			var sector = this.getSector(rx, ry);
			if(sector != null)
			{
				sector.character = "~";
				sector.bg = new Color(0.33, 0.166, 1);
				sector.colorize = false;
				sector.blockWalk = true;
				sector.name = this.riverName;
				riverLocs.push(rx);
				riverLocs.push(ry);
			}
			spread(rx, ry, -0.015, 0.025, 6);

			// Move				
			dir += rng.next(0, 3) - 1;
			if(dir < 0)
				dir = 0;
			if(dir > 4)
				dir = 4;

			if(dir < 2)
				rx--;
			else if(dir > 2)
				rx++;
			if(dir > 0 &&
				dir < 4)
				ry++;
		}
		
		// Add mesas
		count = rng.next(10, 30);
		var mesaLocs = [];
		for(var i = 0; i < count; ++i)
		{
			var rx = rng.next(0, this.width);
			var ry = rng.next(0, this.height);
			var sector = this._sectors[ry * this.width + rx];
			if(!sector.blockWalk)
			{
				sector.blockWalk = true;
				sector.character = "M";
				sector.name = "high mesa";
				mesaLocs.push(rx);
				mesaLocs.push(ry);
			}
		}
		
		// Add the river town
		for(var i = 0; i < 10; ++i)
		{
			var rLoc = rng.next(0, Math.floor(riverLocs.length / 2));
			var tx = riverLocs[rLoc * 2];
			var ty = riverLocs[rLoc * 2 + 1];
			var sector = freeLocationNear(tx, ty, true);
			if(sector != null)
			{
				sector.character = "1";
				sector.fg = Color.black;
				sector.name = "Town";
				sector.destination = new SectorMap("Town", "town", rng.next(), sector.id, 1);
				SectorMap.registerMap(sector.destination);
				break;
			}
		}
		// Add the mesa town
		for(var i = 0; i < 10; ++i)
		{
			var rLoc = rng.next(0, Math.floor(mesaLocs.length / 2));
			var tx = mesaLocs[rLoc * 2];
			var ty = mesaLocs[rLoc * 2 + 1];
			var sector = freeLocationNear(tx, ty, true);
			if(sector != null)
			{
				sector.character = "2";
				sector.fg = Color.black;
				sector.name = "Town";
				sector.destination = new SectorMap("Town", "town", rng.next(), sector.id, 1);
				SectorMap.registerMap(sector.destination);
				break;
			}
		}
		// Add the (more) random town
		for(var i = 0; i < 10; ++i)
		{
			var tx = rng.next(0, this.width);
			var ty = rng.next(0, this.height);
			var sector = freeLocationNear(tx, ty, true);
			if(sector != null)
			{
				sector.character = "3";
				sector.fg = Color.black;
				sector.name = "Town";
				sector.destination = new SectorMap("Town", "town", rng.next(), sector.id, 1);
				SectorMap.registerMap(sector.destination);
				break;
			}
		}
		
		// Add points of interest
		count = rng.next(7, 13);
		var piIndex = 0;
		for(var i = 0; i < count; ++i)
		{
			for(var j = 0; j < 5; ++j)
			{
				var tx = rng.next(0, this.width);
				var ty = rng.next(0, this.height);
				var sector = freeLocationNear(tx, ty, true);
				if(sector != null)
				{
					sector.character = util.letters.charAt(piIndex++);
					sector.fg = Color.black;
					sector.name = "Point of Interest";
					sector.destination = new SectorMap("Point of Interest", rng.nextElement(SectorMap.pointOfInterestTypes), rng.next(), sector.id, 1);
					SectorMap.registerMap(sector.destination);
					break;
				}
			}
		}
		
		// Apply noise and recalculate colors, set heat / moisture for destinations
		for(var i = 0; i < this.width * this.height; ++i)
		{
			var sector = this._sectors[i];
			sector.heat += (rng.nextFloat() * 0.025) - 0.01;
			sector.moisture += (rng.nextFloat() * 0.05) - 0.025;
			sector.recalculateColor();
			if(sector.destination != null)
			{
				sector.destination.heat = sector.heat;
				sector.destination.moisture = sector.moisture;
			}
		}
	},
	/** Called whenever the player's turn is over.
	*/
	_doTurn: function()
	{
		// TODO
	},
	/** Update visibility information for the player.
	 */
	_updateVisibility: function()
	{
		var visDSqr = (this.player.worldMapVisRange + 0.25) * (this.player.worldMapVisRange + 0.25);
		var ix, iy, dx, dy, ofs;
		for(iy = this.player.y - this.player.worldMapVisRange; iy <= this.player.y + this.player.worldMapVisRange; ++iy)
		{
			for(ix = this.player.x - this.player.worldMapVisRange; ix <= this.player.x + this.player.worldMapVisRange; ++ix)
			{
				if(ix < 0 ||
					iy < 0 ||
					ix >= this.width ||
					iy >= this.width)
					continue;
				dx = this.player.x - ix;
				dx *= dx;
				dy = this.player.y - iy;
				dy *= dy;
				if(dx + dy > visDSqr)
					continue;
				ofs = iy * this.width + ix;
				if(this._knownMap[ofs] == 0)
				{
					this._knownMap[ofs] = 1;
					this.renderPosition(ix, iy);
				}
			}
		}
	},
	/** Called when the player re-enters the world map mode.
	@param player The player.
	*/
	onPlayerReenter: function(player)
	{
		this.player = player;
		player.parent = this;
		player.x = this.playerX;
		player.y = this.playerY;
		this._updateVisibility();
	},
	/** Try to use something at the player's position.
	*/
	tryUse: function()
	{
		var sector = this.getPlayerSector();
		if(sector != null &&
			sector.destination != null)
		{
			modeStack.push(new SectorMapMode(sector.destination));
			sector.destination.addPlayer(this.player, true);
			removeMode(worldMapMode);
		}
		else
			this._doTurn();
	},
	/** Try to move the player's position.
	@param x X offset.
	@param y Y offset.
	*/
	tryMovePlayer: function(x, y)
	{
		var nx = this.player.x + x;
		var ny = this.player.y + y;

		// Bounds check
		if(nx < 0 ||
			ny < 0 ||
			nx >= this.width ||
			ny >= this.height)
			return;

		// Walkability check
		var sector = this._sectors[ny * this.width + nx];
		if(sector.blockWalk)
			return;

		var ox = this.player.x;
		var oy = this.player.y;
		this.player.x += x;
		this.player.y += y;
		this.playerX = player.x;
		this.playerY = player.y;
		
		this._updateVisibility();
		this.renderPlayer();
		this.renderPosition(ox, oy);
		this._doTurn();
	},
	/** Get a sector (or null).
	@param x X position.
	@param y Y position.
	*/
	getSector: function(x, y)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return null;
		return this._sectors[y * this.width + x];
	},
	/** Get the player's current sector.
	*/
	getPlayerSector: function()
	{
		var x = this.player.x;
		var y = this.player.y;
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return null;
		return this._sectors[y * this.width + x];
	},
	/** Returns a description of the position.
	@param x X position.
	@param y Y position.
	*/
	getPositionDescription: function(x, y)
	{
		var sector = this.getSector(x, y);
		if(!sector ||
			this._knownMap[y * this.width + x] == 0)
			return "Unknown";
		return sector.name;
	},
	/** Draw a single sector.
	@param x X position.
	@param y Y position.
	*/
	renderPosition: function(x, y)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return;
		var ofs = y * this.width + x;
		if(this._knownMap[ofs] == 0)
			this.buffer.setCell(x + this.xOfs, y + this.yOfs, " ", null, Color.black);
		else
		{
			var sector = this._sectors[ofs];
			this.buffer.setCell(x + this.xOfs, y + this.yOfs, sector.character, sector.fg, sector.bg);
			if(this.player.x == x &&
				this.player.y == y)
				this.renderPlayer();
		}
	},
	/** Draw the player's cursor.
	*/
	renderPlayer: function()
	{
		this.buffer.setCell(this.player.x + this.xOfs, this.player.y + this.yOfs, this.player.character, this.player.fg, this.player.bg);
	},
	/** Draw the map onto a screen buffer.
	*/
	render: function()
	{
		var ix, iy, sector, ofs;
		for(iy = 0; iy < this.height; ++iy)
		{
			for(ix = 0; ix < this.width; ++ix)
			{
				ofs = iy * this.width + ix;
				if(this._knownMap[ofs] == 0)
					this.buffer.setCell(ix + this.xOfs, iy + this.yOfs, " ", null, Color.black);
				else
				{
					sector = this._sectors[iy * this.width + ix];
					this.buffer.setCell(ix + this.xOfs, iy + this.yOfs, sector.character, sector.fg, sector.bg);
				}
			}
		}
		this.renderPlayer();
	},
	/** Draw an overlay on the map
	@param x X position.
	@param y Y position.
	@param c Character.
	@param fg Foreground color.
	@param bg Background color.
	*/
	renderOverlay: function(x, y, c, fg, bg)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return;
		this.buffer.setCell(x + this.xOfs, y + this.yOfs, c, fg, bg);
	}
};
</script>
<script type="text/javascript">
// From File src/SectorMap.js
/** A sector tile.
*/
function SectorTile()
{
	this.name = "Unknown";
	this.character = " ";
	this.fg = Color.white;
	this.bg = Color.black;
	this.blockWalk = false;
	this.blockVis = false;
}
SectorTile.prototype =
{
};

/** A sector map is a map on which gameplay occurs.

Sector maps are generated en-masse at world generation time to establish the
characteristics of the map but feature generation is left until load time.
@param name The name of this map.
@param type The type string for this map.
@param seed The Rrng seed for this map.
@param id Id number of the map. This corrosponds to the world map sectors.
@param level The level of the mpa, starting at 1.
*/
function SectorMap(name, type, seed, id, level)
{
	this.buffer = host.screen;
	this.xOfs = 0;
	this.yOfs = 0;
	this.width = 40;
	this.height = 32;
	this.name = name;
	this.type = type;
	this.seed = seed;
	this.id = id;
	this.level = level;
	this.hasBeenGenerated = false;
	this.heat = 0;
	this.moisture = 0;
	this._tiles = [];
	this._ents = [];
	this._blockWalkMap = [];
	this._blockVisMap = [];
	this._entCountMap = [];
	this._visSet = [];
	this._lastVisSet = [];
	this._knownMap = [];
	this._defaultX = 0;
	this._defaultY = 10;
	this._player = null;
}
SectorMap._registeredMaps = {};
SectorMap._saveData = {};
/** Add a map to the pool of managed maps.
@param map Map to add.
*/
SectorMap.registerMap = function(map)
{
	var token = map.id + "_" + map.level;
	if(SectorMap._registeredMaps[token] != undefined)
		throw new Error("Duplicate sector map registered " + token);
	SectorMap._registeredMaps[token] = map;
}
/** Get the save data for a sector map.
 *@param map The map to get save data for.
 */
SectorMap.getSaveFor = function(map)
{
	var token = map.id + "_" + map.level;
	return SectorMap._saveData[token];
}
/** Generate save data for all registered maps.
*/
SectorMap.getSaveData = function()
{
	var ret = {};
	for(var o in SectorMap._registeredMaps)
	{
		if(!SectorMap._registeredMaps.hasOwnProperty(o))
			continue;
		var map = SectorMap._registeredMaps[o];
		var token = map.id + "_" + map.level;
		if(map.hasBeenGenerated)
		{
			var mapData = [];
			map.serialize(mapData);
			ret[token] = mapData;
		}
		else if(SectorMap._saveData.hasOwnProperty(token))
			ret[token] = SectorMap._saveData[token];
	}
	return ret;
}
/** Clear all sector maps.
 *
 *Do this when loading a game or starting a new game.
 */
SectorMap.unregisterAllMaps = function()
{
	SectorMap._registeredMaps = {};
}
/** Load sector map data from a save file.
 *@param data Save data.
 */
SectorMap.loadSaveData = function(data)
{
	SectorMap._saveData = data;
}
SectorMap.pointOfInterestTypes =
[
	"cave",
	"zombietown",
	"base"
];
SectorMap.prototype =
{
	serialize: function(s)
	{
		var i;

		// Serialize all entities backwards
		for(i = this._ents.length - 1; i >= 0 ; --i)
		{
			var ent = this._ents[i];

			// Skip the player's entity
			if(ent == player)
				continue;
			
			s.push(Entity.getSerializedForm(ent));
		}

		// Save visibility information
		var visRle = [];
		var lastValue = 0;
		var count = 0;
		for(i = 0; i < this._knownMap.length; ++i)
		{
			if(this._knownMap[i] == lastValue)
				++count;
			else
			{
				visRle.push(count);
				lastValue = this._knownMap[i];
				count = 1;
			}
		}
		visRle.push(count);
		s.push(visRle);
	},
	deserialize: function(s)
	{
		// Load visibility information.
		var visRle = s.pop();
		var i, j, visible = false, ofs = 0;
		for(i = 0; i < visRle.length; ++i)
		{
			for(j = 0; j < visRle[i]; ++j)
			{
				this._knownMap[ofs++] = visible ? 1 : 0;
			}
			visible = !visible;
		}
		
		// Deserialize all entities
		this._ents = [];
		while(true)
		{
			var data = s.pop();
			if(!data)
				break;
			this._ents.push(Entity.fromSerializedData(data));
		}
	},
	/** Generate the features of the map including mobiles and objects.
	*/
	generate: function()
	{
		if(!this.hasBeenGenerated)
		{
			this.hasBeenGenerated = true;

			var rng = new Rrng(this.seed);
			var self = this, i;

			// Create all the tiles and vis set data points
			this._tiles = [];
			for(i = 0; i < this.width * this.height; ++i)
			{
				this._tiles.push(new SectorTile());
				this._visSet.push(0);
				this._lastVisSet.push(0);
				this._knownMap.push(0);
			}

			// Start by laying down the base colors if we have heat and moisture values.
			if(this.heat > 0)
			{
				var mf = Math.min(Math.max(this.moisture, 0), 0.33);
				var baseColor = new Color(
					0.66 * this.heat,
					(0.33 + (0.33 - mf)) * this.heat,
					0
				);
				for(i = 0; i < this.width * this.height; ++i)
				{
					this._tiles[i].bg = new Color(
						baseColor.r + rng.nextFloat() * 0.1 - 0.05,
						baseColor.g + rng.nextFloat() * 0.025 - 0.02,
						baseColor.b
					);
					this._tiles[i].name = "desert floor";
				}
			}

			// DEBUG Random rats, yay!
			for(i = 0; i < 20; ++i)
			{
				var dx = rng.next(0, this.width);
				var dy = rng.next(0, this.height);
				var door = new RatMobile();
				door.x = dx;
				door.y = dy;
				this._ents.push(door);
			}

			// TODO Call the type-specific map generator

			// Try to load the entities from saved data if present.
			var save = SectorMap.getSaveFor(this);
			if(save)
				this.deserialize(save);

			// Build the base flag maps and set entity parents
			for(i = 0; i < this.width * this.height; ++i)
			{
				var tile = this._tiles[i];
				this._blockWalkMap.push(tile.blockWalk ? 1 : 0);
				this._blockVisMap.push(tile.blockVis ? 1 : 0);
				this._entCountMap.push(0);
			}
			for(i = 0; i < this._ents.length; ++i)
			{
				var ent = this._ents[i];
				if(ent.x < 0 ||
					ent.y < 0 ||
					ent.x >= this.width ||
					ent.y >= this.height)
					continue;
				var idx = ent.y * this.width + ent.x;
				++this._entCountMap[idx];
				if(ent.blockWalk)
					++this._blockWalkMap[idx];
				if(ent.blockVis)
					++this._blockVisMap[idx];
				ent.parent = this;
				ent.onArriveAtPosition(true);
			}
		}
		// Re-initialize entities
		for(i = 0; i < this._ents.length; ++i)
		{
			var ent = this._ents[i];
			if(ent.isMobile)
				ent.createController();
		}
	},
	/** Called to do a turn for all entities in the map.
	*/
	doTurn: function()
	{
		var updateList = [];
		for(var i = 0; i < this._ents.length; ++i)
		{
			updateList.push(this._ents[i]);
		}
		for(var i = 0; i < updateList.length; ++i)
		{
			var ent = updateList[i];
			if(!ent.isMobile ||
				ent == player)
				continue;
			ent.time += player.speed;
			while(ent.time > 0)
			{
				ent.doTurn();
				ent.time -= ent.speed;
			}
		}
	},
	/** Adds the player's entity to the map.
	@param player The player's entity.
	@param fromWorldMap True if the player is comming from the world map.
	*/
	addPlayer: function(player, fromWorldMap)
	{
		if(fromWorldMap)
		{
			player.x = this._defaultX;
			player.y = this._defaultY;
		}
		this._player = player;
		this.addEntity(player);
	},
	/** Add an entity to the map.
	@param ent Entity to add.
	*/
	addEntity: function(ent)
	{
		this._ents.push(ent);
		ent.parent = this;
		this._onArrive(ent);
		this.renderPosition(ent.x, ent.y);
	},
	/** Remove an entity from the map.
	@param ent Entity to remove.
	*/
	removeEntity: function(ent)
	{
		this._onLeave(ent);
		ent.parent = null;
		var idx = this._ents.indexOf(ent);
		if(idx < 0)
			return;
		this._ents.splice(idx, 1);
		this.renderPosition(ent.x, ent.y);
	},
	/** Try to move an entity.
	@param ent Entity to move.
	@param x New X location.
	@param y New Y location.
	@returns true if successful, false otherwise.
	*/
	tryMoveEntity: function(ent, x, y)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return false;
		if(this._blockWalkMap[y * this.width + x] > 0)
			return false;
		
		this.removeEntity(ent);
		var lastX = ent.x, lastY = ent.y;
		ent.x = x;
		ent.y = y;
		this.addEntity(ent);
		this.renderPosition(lastX, lastY);
		return true;
	},
	/** Return the tile at a position or null if out of bounds.
	@param x X position.
	@param y Y position.
	 */
	getTile: function(x, y)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return null;
		return this._tiles[y * this.width + x];
	},
	/** Return an array of all of the entities at a position.
	@param x X parameter.
	@param y Y parameter.
	*/
	getEntitiesAtPosition: function(x, y)
	{
		var ret = [], i, ent;
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return ret;
		if(this._entCountMap[y * this.width + x] > 0)
		{
			for(i = 0; i < this._ents.length; ++i)
			{
				ent = this._ents[i];
				if(ent.x != x ||
					ent.y != y)
					continue;
				ret.push(ent);
			}
		}
		return ret;
	},
	/** Returns a description of the position.
	@param x X position.
	@param y Y position.
	*/
	getPositionDescription: function(x, y)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return "Unknown";
		// TODO Implement visibility
		var i = y * this.width + x;
		var desc = this._tiles[i].name;
		if(this._entCountMap[i] > 0)
		{
			var foundCount = 0;
			for(i = this._ents.length - 1; i >= 0; --i)
			{
				var ent = this._ents[i];
				if(ent.x != x ||
					ent.y != y)
					continue;
				if(foundCount == 0)
					desc += ": ";
				else
					desc += ", ";
				++foundCount;
				desc += ent.name;
			}
		}
		return desc;
	},
	/** Called when an entity arrives at a position.
	@param ent The entity.
	*/
	_onArrive: function(ent)
	{
		if(ent.x < 0 ||
			ent.y < 0 ||
			ent.x >= this.width ||
			ent.y >= this.height)
			return;
		var i = ent.y * this.width + ent.x;
		if(ent.blockWalk)
			++this._blockWalkMap[i];
		if(ent.blockVis)
			++this._blockVisMap[i];
		++this._entCountMap[i];
		ent.onArriveAtPosition(false);
		if(ent == player)
		{
			this._updateVisibilitySetFrom(ent);
			this._applyVisibilitySet();
		}
	},
	/** Called when an entity leaves a position.
	@param ent The entity.
	*/
	_onLeave: function(ent)
	{
		if(ent.x < 0 ||
			ent.y < 0 ||
			ent.x >= this.width ||
			ent.y >= this.height)
			return;
		var i = ent.y * this.width + ent.x;
		if(ent.blockWalk)
			--this._blockWalkMap[i];
		if(ent.blockVis)
			--this._blockVisMap[i];
		--this._entCountMap[i];
		ent.onLeavePosition();
	},
	/** Determines if point b is visible from point a.
	 *@param ax X position of point a.
	 *@param ay Y position of point a.
	 *@param bx X position of point b.
	 *@param by Y position of point b.
	 *@param range Optional range for the test.
	 */
	isVisibleFrom: function(ax, ay, bx, by, range)
	{
		var dx = ax - bx;
		var dy = ay - by;
		if(range &&
			dx * dx + dy * dy > range * range)
			return false;
		var absdx = dx;
		var absdy = dy;
		if(absdx < 0) absdx *= -1;
		if(absdy < 0) absdy *= -1;

		// Line driver, based on Bresenham's line algorithm
		// If the line is steeper that 45% reflect the line
		var steep = absdy > absdx;
		var x0, x1, y0, y1;
		if(steep)
		{
			x0 = ay;
			x1 = by;
			y0 = ax;
			y1 = bx;
		}
		else
		{
			x0 = ax;
			x1 = bx;
			y0 = ay;
			y1 = by;
		}
		dx = x1 - x0;
		dy = y1 - y0;
		absdx = dx;
		absdy = dy;
		if(absdx < 0) absdx *= -1;
		if(absdy < 0) absdy *= -1;
		var error = absdx / 2;
		var stepy = 1;
		if(y0 > y1) stepy = -1;
		var stepx = 1;
		if(x0 > x1) stepx = -1;
		var realx, realy;
		var liney = y0;
		var linex = x0;
		var steps = x1 - x0;
		if(steps < 0) steps *= -1;
		// NOTE: This for loop omitts the first and last positions
		for(var iStep = 1; iStep < steps; ++iStep)
		{
			if(steep)
			{
				realx = liney;
				realy = linex;
			}
			else
			{
				realx = linex;
				realy = liney;
			}
			error -= absdy;
			if(error < 0)
			{
				liney += stepy;
				error += absdx;
			}

			// Out of bounds tests always return false.
			if(realx < 0 ||
				realy < 0 ||
				realx >= this.width ||
				realy >= this.height)
				return false;

			// At this point, realx and realy are our absolute map coordinates along this line.
			// If this map position blocks visibility we return false;
			if(this._blockVisMap[realy * this.width + realx] > 0)
				return false;
		}

		// If we get here without returning false we have a line of sight.
		return true;
	},
	/** Updates the visibility set data based on an entity.
	 *
	 * Visibility method that uses a line-of-sight test for every map position within the visible
	 * range. Positions that were marked as visible in previous line-of-sight tests will be skipped.
	 * A post-processing step is then applied to clean up the most common artifacts caused by this
	 * type of visibility method.
	 *
	 * This is a useful visibility method to use for very ranged-combat intensive games as any
	 * visibile mobile will nessecarily have line-of-sight to and from the player (in most cases).
	 *
	 * The data format for the visibility buffer is as follows:
	 * 0	Position is not visible
	 * 1	Position was marked visible as a result of the post-processing phase
	 * 2	Position was marked visible as the point of obstruction of a line of sight
	 * 4	Position was marked visible as it is within line of sight
	 *
	 * This is significant only to the post-processing phase, which uses sums of neighbor cell
	 * visibility to determine if a cell should be marked visible.
	 *
	 * Here's how the post processing works:
	 *
	 * We look at every position in our visibility range, plus a one-cell periffery. If a position's
	 * TILE blocks visibility AND it is not already marked visibile then we do this:
	 *
	 * Add up the visibility bytes from all eight adjacent cells who's tiles. If any one of these tiles
	 * has a value of 4 we mark our position as visible too. This gets the "looking down a wall"
	 * and "interior corner" edge cases which cause most of the artifacting with basic ray casting. This
	 * does create a little bit of bleeding though.
	 *
	 * This visibility method was ported from an unreleased C# project I did about a year prior and is
	 * not 100% optimized for JavaScript. As it is it is not suitable for large maps. The origional C#
	 * implementation used a helper class that allowed this.
	 *@param ent The entity to use.
	 */
	_updateVisibilitySetFrom: function(ent)
	{
		var i;

		// Swap vis sets
		for(i = 0; i < this._visSet.length; ++i)
		{
			this._lastVisSet[i] = this._visSet[i];
			this._visSet[i] = 0;
		}

		// Data setup
		var mobVisRangeSquared = (ent.visibilityRange - 0.5) * (ent.visibilityRange - 0.5);
		var mx = ent.x;
		var my = ent.y;

		// Spiral driver
		var spMinX = mx - ent.visibilityRange;
		var spMinY = my - ent.visibilityRange;
		var spMaxX = mx + ent.visibilityRange;
		var spMaxY = my + ent.visibilityRange;
		var ix = spMinX;
		var iy = spMinY;
		var spStepX = 1;
		var spStepY = 0;
		while(true)
		{
			// End of spiral, break
			if(spMinX == spMaxX ||
				spMinY == spMaxY)
				  break;

			// Right extent
			if(spStepX == 1 &&
				ix >= spMaxX - 1)
			{
				spStepX = 0;
				spStepY = 1;
				spMinY++;
			}
			// Left extent
			if(spStepX == -1 &&
				ix <= spMinX)
			{
				spStepX = 0;
				spStepY = -1;
				spMaxY--;
			}
			// Lower extent
			if(spStepY == 1 &&
				iy >= spMaxY - 1)
			{
				spStepY = 0;
				spStepX = -1;
				spMaxX--;
			}
			// Upper extent
			if(spStepY == -1 &&
				iy <= spMinY)
			{
				spStepY = 0;
				spStepX = 1;
				spMinX++;
			}

			// Process position. We do this in a do ... while(false) so we can use break
			// to fall through to the next spiral loop with break.
			do
			{
				// Out of bounds short-circut
				if(ix < 0 ||
					iy < 0 ||
					ix >= this.width ||
					iy >= this.height)
					break;

				// Already set to visible short-circut
				if(this._visSet[iy * this.width + ix] > 0)
					break;

				// Out-of-Range Short Circut
				var dx = ix - mx;
				var dy = iy - my;
				if(dx * dx + dy * dy > mobVisRangeSquared)
					break;
				var absdx = dx;
				var absdy = dy;
				if(absdx < 0) absdx *= -1;
				if(absdy < 0) absdy *= -1;

				// Line driver, based on Bresenham's line algorithm
				// If the line is steeper that 45% reflect the line
				var steep = absdy > absdx;
				var x0, x1, y0, y1;
				if(steep)
				{
					x0 = my;
					x1 = iy;
					y0 = mx;
					y1 = ix;
				}
				else
				{
					x0 = mx;
					x1 = ix;
					y0 = my;
					y1 = iy;
				}
				dx = x1 - x0;
				dy = y1 - y0;
				absdx = dx;
				absdy = dy;
				if(absdx < 0) absdx *= -1;
				if(absdy < 0) absdy *= -1;
				var error = absdx / 2;
				var stepy = 1;
				if(y0 > y1) stepy = -1;
				var stepx = 1;
				if(x0 > x1) stepx = -1;
				var realx, realy;
				var liney = y0;
				var linex = x0;
				var steps = x1 - x0;
				if(steps < 0) steps *= -1;
				for(var iStep = 0; iStep <= steps; ++iStep)
				{
					if(steep)
					{
						realx = liney;
						realy = linex;
					}
					else
					{
						realx = linex;
						realy = liney;
					}
					error -= absdy;
					if(error < 0)
					{
						liney += stepy;
						error += absdx;
					}

					// At this point, realx and realy are our absolute map coordinates along this line.
					// If this map position blocks visibility we set it to blocking visible and bail
					// out of the line driver.
					if(realx >= 0 &&
						realy >= 0 &&
						realx < this.width &&
						realy < this.height)
					{
						if(this._blockVisMap[realy * this.width + realx] > 0)
						{
							this._visSet[realy * this.width + realx] = 2;
							break;
						}
						// Otherwise we have a map position that is visible from the reference mobile.
						this._visSet[realy * this.width + realx] = 4;
					}
					linex += stepx;
				}
			} while(false);

			// Step position
			ix += spStepX;
			iy += spStepY;
		}

		// Postprocessing, see method remarks for logic
		spMinX = mx - ent.visibilityRange;
		spMinY = my - ent.visibilityRange;
		spMaxX = mx + ent.visibilityRange;
		spMaxY = my + ent.visibilityRange;
		for(iy = spMinY - 1; iy < spMaxY + 1; ++iy)
		{
			for(ix = spMinX - 1; ix < spMaxX + 1; ++ix)
			{
				// Out of bounds short-circut
				if(ix < 0 ||
					iy < 0 ||
					ix >= this.width ||
					iy >= this.height)
					break;

				var tile = this._tiles[iy * this.width + ix];
				if(!tile.blockVis ||
					this._visSet[iy * this.width + ix] > 0)
					continue;

				// Inspect West
				if(ix > 0)
				{
					// W
					if(this._visSet[(iy) * this.width + (ix - 1)] >= 4)
					{
						this._visSet[(iy) * this.width + (ix)] = 1;
						continue;
					}
					// NW
					if(iy > 0 &&
						this._visSet[(iy - 1) * this.width + (ix - 1)] >= 4)
					{
						this._visSet[(iy) * this.width + (ix)] = 1;
						continue;
					}
					// SW
					if(iy < this.height - 1 &&
						this._visSet[(iy + 1) * this.width + (ix - 1)] >= 4)
					{
						this._visSet[(iy) * this.width + (ix)] = 1;
						continue;
					}
				}
				// Inspect East
				if(ix < this.width - 1)
				{
					// E
					if(this._visSet[(iy) * this.width + (ix + 1)] >= 4)
					{
						this._visSet[(iy) * this.width + (ix)] = 1;
						continue;
					}
					// NE
					if(iy > 0 &&
						this._visSet[(iy - 1) * this.width + (ix + 1)] >= 4)
					{
						this._visSet[(iy) * this.width + (ix)] = 1;
						continue;
					}
					// SE
					if(iy < this.height - 1 &&
						this._visSet[(iy + 1) * this.width + (ix + 1)] >= 4)
					{
						this._visSet[(iy) * this.width + (ix)] = 1;
						continue;
					}
				}
				// Inspect North
				if(iy > 0 &&
					this._visSet[(iy - 1) * this.width + (ix)] >= 4)
				{
					this._visSet[(iy) * this.width + (ix)] = 1;
					continue;
				}
				// Inspect South
				if(iy < this.height - 1 &&
					this._visSet[(iy + 1) * this.width + (ix)] >= 4)
				{
					this._visSet[(iy) * this.width + (ix)] = 1;
					continue;
				}
			}
		}
	},
	/** Analyses the last visibility set and the current visibility set and
	 *takes appropriote action such as marking tiles as known and redrawing
	 *positions that have changed.
	 */
	_applyVisibilitySet: function()
	{
		var ix, iy, ofs = 0;
		for(iy = 0; iy < this.height; ++iy)
		{
			for(ix = 0; ix < this.width; ++ix)
			{
				if(this._visSet[ofs] > 0)
					this._knownMap[ofs] = 1;
				if((this._lastVisSet[ofs] > 0) !=
					(this._visSet[ofs] > 0))
					this.renderPosition(ix, iy);
				++ofs;
			}
		}
	},
	/** Render a map location.
	@param x X location.
	@param y Y location.
	*/
	renderPosition: function(x, y)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return;
		// Visibility check
		var ofs = y * this.width + x;
		var tile = this._tiles[ofs];
		if(this._visSet[ofs] > 0)
		{
			this.buffer.setCell(x + this.xOfs, y + this.yOfs, tile.character, tile.fg, tile.bg);
			if(this._entCountMap[ofs] > 0)
			{
				for(var i = 0; i < this._ents.length; ++i)
				{
					ent = this._ents[i];
					if(ent.x != x ||
						ent.y != y)
						continue;
					this.buffer.setCell(ent.x + this.xOfs, ent.y + this.yOfs, ent.character, ent.fg, ent.bg);
				}
			}
		}
		else if(this._knownMap[ofs] > 0)
		{
			this.buffer.setCell(x + this.xOfs, y + this.yOfs, tile.character, tile.fg.newScaled(0.5), tile.bg.newScaled(0.5));
		}
		else
		{
			this.buffer.setCell(x + this.xOfs, y + this.yOfs, " ", null, Color.black);
		}
	},
	/** Render the entire map.
	*/
	render: function()
	{
		var ix, iy, i, tile, ent, ofs;
		for(iy = 0; iy < this.height; ++iy)
		{
			for(ix = 0; ix < this.width; ++ix)
			{
				ofs = iy * this.width + ix;
				tile = this._tiles[ofs];
				if(this._visSet[ofs] > 0)
				{
					this.buffer.setCell(ix + this.xOfs, iy + this.yOfs, tile.character, tile.fg, tile.bg);
				}
				else if(this._knownMap[ofs] > 0)
				{
					this.buffer.setCell(ix + this.xOfs, iy + this.yOfs, tile.character, tile.fg.newScaled(0.5), tile.bg.newScaled(0.5));
				}
				else
				{
					this.buffer.setCell(ix + this.xOfs, iy + this.yOfs, " ", null, Color.black);
				}
			}
		}
		for(i = 0; i < this._ents.length; ++i)
		{
			ent = this._ents[i];
			if(this._visSet[ent.y * this.width + ent.x] > 0)
				this.buffer.setCell(ent.x + this.xOfs, ent.y + this.yOfs, ent.character, ent.fg, ent.bg);
		}
	},
	/** Draw an overlay on the map
	@param x X position.
	@param y Y position.
	@param c Character.
	@param fg Foreground color.
	@param bg Background color.
	*/
	renderOverlay: function(x, y, c, fg, bg)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return;
		this.buffer.setCell(x + this.xOfs, y + this.yOfs, c, fg, bg);
	}
};
</script>
<script type="text/javascript">
// From File src/data/text.js
/* Generated by textcompile.pl */
var text =
{
	'default': '',
	'General Help': '                  %d0General Help%f0\n\nShambletown is designed to be played with either\na full keyboard or the numeric keypad. In the\ngame and in menus the %e0arrow keys%f0 and the %e08%f0,\n%e02%f0, %e04%f0 and %e06%f0 keys can be used to move up, down,\nleft and right respectively. Try using them now\nto scroll this text up and down.\n\nAt any time you may press the %e0?%f0 or %e0/%f0 key to\ndisplay helpful information about the interface\nyou are currently using. Try it now!\n\nShambletown comes with plenty of documentation\nbuilt right into the game. Press the %e0Space Bar%f0\nor the %e00%f0 key while not in another menu to access\nthe main game menu. Here you can access the help\ndocumentation library.\n\nYou can save your game\'s progress when on the\nmain world map by accessing the main game menu\nand selecting "Save and Quit". Keep in mind that\nShambletown is meant to be a challenge, and death\nis permanent. When you die your saved game is\nimmediately deleted. Saved games are only\nintended to allow the player to play the game\nover several play sessions, not to roll-back poor\ndecisions or outcomes.\n\nIn menus the %e0Enter%f0 key is used to confirm a\ndecision. The %e0Escape%f0 and %e0+%f0 keys are used to\ncancel a decision or exit the menu. When you are\ndone reading this text use any of these keys to\ncontinue.',
	'About': 'Shambletown is a Roguelike Role Playing Adventure\ngame. Set three hundred years after the Zombie\nApocalypse, Shambletown follows the story of a\nlone drifter in the ruins of the American\nSouthwest as he searches for eternal life and\nunravels the mysteries of the Church of the\nUndying Flesh.',
	'Introduction': 'You are Eddy Vance.'
};
</script>
<script type="text/javascript">
// From File src/Entity/Entity.js
/** Map entity base class.
@param c Character used for this entity.
@param fg Foreground color.
@param bg Background color.
@param blockWalk Does this entity block walkability?
@param blockVis Does this entity block visibility?
*/
function Entity(name, c, fg, bg, blockWalk, blockVis)
{
	this.name = name;
	this.article = "a";
	this.character = c;
	this.fg = fg;
	this.bg = bg;
	this.blockWalk = blockWalk;
	this.blockVis = blockVis;
	this.x = 0;
	this.y = 0;
	this.parent = null;
}
Entity.prototype =
{
	isEntity: true,
	serialize: function(s)
	{
		s.push(this.x, this.y);
	},
	deserialize: function(s)
	{
		this.y = s.pop();
		this.x = s.pop();
	},
	/** Called when an entity tries to use this entity.
	@param user The entity using this entity.
	@param target The entity being used on (optional).
	*/
	use: function(user, target)
	{
	},
	/** Called when the entity arrives at a position on the sector map.
	 *@param isLoad True if this call was the result of map generation or loading, false otherwise.
	 */
	onArriveAtPosition: function(isLoad)
	{
	},
	/** Called when the entity leaves a position on the sector map.
	 */
	onLeavePosition: function()
	{
	}
};
Entity._ctors = {};
Entity.registerCtor = function(ctor, id)
{
	if(Entity._ctors[id] !== undefined)
		throw new Error("Duplicate CTOR id " + id);
	ctor.prototype.__serializationId = id;
	Entity._ctors[id] = ctor;
}
Entity.objectFromCtor = function(id)
{
	var ctor = Entity._ctors[id];
	if(ctor === undefined)
		throw new Error("Unregistered CTOR id requested " + id);
	return new ctor();
}
/** Returns the serialized form of an Entity.
@param ent The entity to serialize.
*/
Entity.getSerializedForm = function(ent)
{
	var ret = [];
	ent.serialize(ret);
	ret.push(ent.__serializationId);
	return ret;
}
/** Returns an entity object from a serailized form.
@param data The serialized form data array.
 */
Entity.fromSerializedData = function(data)
{
	var ctorId = data.pop();
	var ret = Entity.objectFromCtor(ctorId);
	ret.deserialize(data);
	return ret;
}
</script>
<script type="text/javascript">
// From File src/Entity/Door.js
/** A door entity.
*/
function EntityDoor()
{
	// 0 = open, 1 = closed, >1 = locked, status = lockId
	this.status = 1;
	this._updateState();
}
EntityDoor.prototype = new Entity("a Door", "+", Color.white, null, true, true);
EntityDoor.prototype.serialize = function(s)
{
	Entity.prototype.serialize.call(this, s);
	s.push(this.status);
}
EntityDoor.prototype.deserialize = function(s)
{
	this.status = s.pop();
	Entity.prototype.deserialize.call(this, s);
	this._updateState();
}
EntityDoor.prototype.use = function(user, target)
{
	var parent = this.parent;
	if(parent != null)
		parent.removeEntity(this);
	if(this.status < 1)
		this.status = 1;
	else if(this.status == 1)
		this.status = 0;
	// TODO Support locked doors
	this._updateState();
	if(parent != null)
		parent.addEntity(this);
}
EntityDoor.prototype._updateState = function()
{
	this.character = this.status > 0 ? "+" : "/";
	this.blockVis = this.blockWalk = this.status > 0 ? true : false;
}
Entity.registerCtor(EntityDoor, 0);
</script>
<script type="text/javascript">
// From File src/Entity/Corpse.js
/** Corpse entity.
 */
function CorpseEntity()
{
}
CorpseEntity.prototype = new Entity("corpse", "%", Color.white, null, false, false);
CorpseEntity.prototype._bloodPatternData =
[
	0, 0, 0.4,
	-1, 0, 0.15,
	1, 0, 0.15,
	0, -1, 0.15,
	0, 1, 0.15
];
CorpseEntity.prototype._bloodBaseColor = new Color(0.7, 0, 0);
CorpseEntity.prototype.onArriveAtPosition = function(isLoad)
{
	// A little bit of blood spatter, yay!
	var ix, iy, a, i, c;
	for(i = 0; i < this._bloodPatternData.length; i += 3)
	{
		ix = this._bloodPatternData[i + 0];
		iy = this._bloodPatternData[i + 1];
		a = this._bloodPatternData[i + 2] * Math.random() + 0.1;
		var tile = this.parent.getTile(this.x + ix, this.y + iy);
		if(tile)
		{
			c = CorpseEntity.prototype._bloodBaseColor.newScaled(1.1 - Math.random() * 0.2);
			if(tile.bg)
				tile.bg = tile.bg.alphaBlend(c, a);
			if(tile.fg)
				tile.fg = tile.fg.alphaBlend(c, a);
			this.parent.renderPosition(this.x + ix, this.y + iy);
		}
	}
}
Entity.registerCtor(CorpseEntity, 4);
</script>
<script type="text/javascript">
// From File src/Entity/Mobile/Mobile.js
/** Mobile base class.
@param name The name of this mobile.
@param c Character used for this entity.
@param fg Foreground color.
@param bg Background color.
@param hp Hit points.
@param speed The speed of the mobile;
@param controllerDesc Controller description, an object containing fields named for the behaviors defined by Controller.
*/
function Mobile(name, c, fg, bg, hp, speed, controllerDesc)
{
	this.name = name;
	this.character = c;
	this.fg = fg;
	this.bg = bg;
	this.hp = hp;
	this.hpMax = hp;
	this.speed = speed;
	this.controllerDesc = controllerDesc;
	this.controller = null;
	this.skills =
	{
		unarmed: 1,
		melee: 1,
		firearms: 1
	};
	this.visibilityRange = 10;
	this.init();
}
Mobile.prototype = new Entity("Mobile", "@", Color.white, Color.black, true, false);
/** Initialize routine.
 */
Mobile.prototype.init = function()
{
	this.time = Math.floor(rand.next(0, this.speed) - this.speed / 2);
}
Mobile.prototype.isMobile = true;
Mobile.prototype.serialize = function(s)
{
	Entity.prototype.serialize.call(this, s);
	s.push(this.hp, this.hpMax, this.time);
}
Mobile.prototype.deserialize = function(s)
{
	this.time = s.pop();
	this.hpMax = s.pop();
	this.hp = s.pop();
	Entity.prototype.deserialize.call(this, s);
}
/** Checks the mobile's skill against a set difficulty.
 *@param skillName The name of the skill to check.
 *@param difficulty The difficulty level to check against.
 */
Mobile.prototype.skillCheck = function(skillName, difficulty)
{
	var skill = this.skills[skillName];
	if(!skill)
		return false;
	if(!difficulty)
		return true;
	return Math.random() * 10 < ((skill - difficulty) + 5)
}
/** Attacks another mobile.
 *@param target The target of the attack.
 *@returns True if the attack was possible, false if it was not.
 */
Mobile.prototype.attack = function(target)
{
	// Range and LOS check
	if(!this.parent.isVisibleFrom(this.x, this.y, target.x, target.y, this.getCombatRange()))
		return false;

	// Skill check
	if(this.skillCheck(this.getCombatSkillName(), target.skills[target.getCombatSkillName()]))
	{
		// TODO Move the damage roll to the weapon objects
		var damage = rand.next(4, 7);
		this._doHitMessage(target, damage);
		target.takeDamage(damage, this);
	}
	else
		this._doMissMessage(target);
	return true;
}
/** Take damage.
 *@param amount The amount of damage to take.
 *@param source The source of the damage.
 */
Mobile.prototype.takeDamage = function(amount, source)
{
	this.hp -= amount;
	if(this.hp <= 0)
		this.die();
	if(source.isMobile &&
		this.controller)
		this.controller.wasHitBy(source);
}
/** Hit message proceedure.
 *@param target The target of the hit.
 *@param amount The amount of damage delt.
 */
Mobile.prototype._doHitMessage = function(target, amount)
{
	if(this == player)
		logWindow.pushMessage("%a0You hit the " + target.name + "! [" + amount + "]");
	else
	{
		if(target == player)
			logWindow.pushMessage("%90The " + this.name + " hit you! [" + amount + "]");
		else
			logWindow.pushMessage("%f0The " + this.name + " hit the " + target.name + ". [" + amount + "]");
	}
}
/** Miss message proceedure.
 *@param target The target of the missed attack.
 */
Mobile.prototype._doMissMessage = function(target)
{
	if(this == player)
		logWindow.pushMessage("%90You miss the " + target.name + ".");
	else
	{
		if(target == player)
			logWindow.pushMessage("%a0The " + this.name + " missed you.");
		else
			logWindow.pushMessage("%f0The " + this.name + " missed the " + target.name + ".");
	}
}
/** Death message proceedure.
 */
Mobile.prototype._doDeathMessage = function()
{
	if(this == player)
		logWindow.pushMessage("%90You died.");
	else
		logWindow.pushMessage("%a0The " + this.name + " died.");
}
/** Death proceedure.
 */
Mobile.prototype.die = function()
{
	this._doDeathMessage();
	var corpse = this.getCorpseEntity();
	corpse.x = this.x;
	corpse.y = this.y;
	var parent = this.parent;
	parent.removeEntity(this);
	parent.addEntity(corpse);
}
/** Gets the corpse entity for this mobile.
 */
Mobile.prototype.getCorpseEntity = function()
{
	return new CorpseEntity();
}
/** Returns the mobile's current combat skill.
 */
Mobile.prototype.getCombatSkillName = function()
{
	// TODO Get skill name from weapon object
	return "unarmed";
}
/** Returns the mobile's combat range.
 */
Mobile.prototype.getCombatRange = function()
{
	// TODO Get this from the weapon object
	return 1;
}
/** Do a turn for this mobile.
 */
Mobile.prototype.doTurn = function()
{
	this.controller.doTurn();
	return;
}
/** Create this mobile's controller.
 *
 * This should be called once for every mobile when the player enters the map.
 */
Mobile.prototype.createController = function()
{
	this.controller = new Controller(
		this,
		this.controllerDesc.sense,
		this.controllerDesc.alert,
		this.controllerDesc.idle,
		this.controllerDesc.wimpy,
		this.controllerDesc.flee,
		this.controllerDesc.attack,
		this.controllerDesc.follow
	);
}
</script>
<script type="text/javascript">
// From File src/Entity/Mobile/Controller.js
/** A controller is a Mobile's AI implementation and is responsible for making
 * desicions for the mobile and carrying out those decisions.
 *
 * The controller system defines these stages in the decision making process for
 * mobiles:
 * target		Acquire a target for combat
 * combat		Engage the target in combat
 *
 * The controller system for Shambletown identifies these methods used int the
 * mobile decision making stages:
 * sense		How does the mobile sense targets, and what targets does it look for
 * alert		How does the mobile behave when it senses a target
 * idle			How the mobile behaves when it has nothing to do
 * wimpy		How the mobile decides to flee
 * flee			How the mobile flees a danger or threat
 * attack		How the mobile attacks its target
 * follow		How does the mobile follow a target
 *
 * A controller consists of a set of method definitions that are used in the
 * decision making process. These methods are defined within pools below. These
 * decision making methods can be mixed and matched to create complex AIs.
 *
 * @param mobile The mobile the controller is to control.
 * @param sense The name of the sense behavior method.
 * @param alert The name of the alert behavior method.
 * @param idle The name of the idle behavior method.
 * @param wimpy The name of the wimpy behavior method.
 * @param flee The name of the flee behavior method.
 * @param attack The name of the attack behavior method.
 * @param follow The name of the follow behavior method.
 */
function Controller(mobile, sense, alert, idle, wimpy, flee, attack, follow)
{
	this.mobile = mobile;
	this.sense = Controller._sense[sense];
	this.alert = Controller._alert[alert];
	this.idle = Controller._idle[idle];
	this.wimpy = Controller._wimpy[wimpy];
	this.flee = Controller._flee[flee];
	this.attack = Controller._attack[attack];
	this.follow = Controller._follow[follow];
	this.stage = "target";
	this.isFleeing = false;
	this.target = null;
}
/** A sense method accepts no parameters and returns a target mobile or null if
 * no suitable target was found.
 */
Controller._sense =
{
	none: function() { return null; },
	lookForPlayer: function()
	{
		if(this.mobile.parent.isVisibleFrom(this.mobile.x, this.mobile.y, player.x, player.y, this.mobile.visibilityRange))
			return player;
	}
}
/** An alert method is called after this.target is set.
 */
Controller._alert =
{
	none: function() { return; },
	attack: function()
	{
		this.stage = "combat";
	},
	run: function()
	{
		this.stage = "combat";
		this.isFleeing = true;
	}
}
/** An idle method is called whenever there is nothing else to do. There are no
 * preconditions or expectations.
 */
Controller._idle =
{
	none: function() { return; },
	wander: function()
	{
		for(var i = 0; i < 5; ++i)
		{
			if(this.dir === undefined ||
				Math.random() > 0.85)
				this.dir = Math.floor(Math.random() * 4);
			var dx = 0;
			var dy = 0;
			switch(this.dir)
			{
				case 0: dx = 1; break;
				case 1: dx = -1; break;
				case 2: dy = 1; break;
				case 3:
				default: dy = -1; break;
			}
			if(this.mobile.parent.tryMoveEntity(this.mobile, this.mobile.x + dx, this.mobile.y + dy))
				break;
		}
	}
}
/** A wimpy method is called in combat to determine if the mobile should start
 * running. It returns true if the mobile should start running, false otherwise.
 * this.target will be set.
 */
Controller._wimpy =
{
	none: function() { return false; },
	run: function()
	{
		return true;
	}
}
/** A flee method is called after this.target is set and is used to run away
 * from that target.
 */
Controller._flee =
{
	none: function() { return; },
	run: function()
	{
		var dx = this.mobile.x - this.target.x;
		var dy = this.mobile.y - this.target.y;
		dx = dx < 0 ? -1 : dx > 0 ? 1 : Math.random() > 0.5 ? -1 : 1;
		dy = dy < 0 ? -1 : dy > 0 ? 1 : Math.random() > 0.5 ? -1 : 1;
		if(Math.random() > 0.5)
		{
			if(this.mobile.parent.tryMoveEntity(this.mobile, this.mobile.x + dx, this.mobile.y))
				return;
		}
		if(this.mobile.parent.tryMoveEntity(this.mobile, this.mobile.x, this.mobile.y + dy))
			return;
		if(this.mobile.parent.tryMoveEntity(this.mobile, this.mobile.x + dx, this.mobile.y))
			return;
	}
}
/** An attack method is called after this.target is set and is used to attack
 * that target. Returns true if the target was valid for the attack, false if
 * we need to move closer.
 */
Controller._attack =
{
	none: function() { return false; },
	attack: function()
	{
		return this.mobile.attack(this.target);
	}
}
/** A follow method is called after this.target is set and is used to follow
 * that target.
 */
Controller._follow =
{
	none: function() { return; },
	follow: function()
	{
		var dx = this.mobile.x - this.target.x;
		var dy = this.mobile.y - this.target.y;
		dx = dx < 0 ? 1 : dx > 0 ? -1 : 0;
		dy = dy < 0 ? 1 : dy > 0 ? -1 : 0;
		if(dx != 0 &&
			Math.random() > 0.5)
		{
			if(this.mobile.parent.tryMoveEntity(this.mobile, this.mobile.x + dx, this.mobile.y))
				return;
		}
		if(dy != 0 &&
			this.mobile.parent.tryMoveEntity(this.mobile, this.mobile.x, this.mobile.y + dy))
			return;
		if(dx != 0 &&
			this.mobile.parent.tryMoveEntity(this.mobile, this.mobile.x + dx, this.mobile.y))
			return;

		// If we get here we have not moved, try an off-center move maybe
		if(dx == 0 &&
			this.mobile.parent.tryMoveEntity(this.mobile, this.mobile.x + (Math.random() > 0.5 ? -1 : 1), this.mobile.y))
			return;
		if(dy == 0 &&
			this.mobile.parent.tryMoveEntity(this.mobile, this.mobile.x, this.mobile.y + (Math.random() > 0.5 ? -1 : 1)))
			return;
	}
}
Controller.prototype =
{
	/** Execute a turn on behalf of our mobile. */
	doTurn: function()
	{
		// First we validate that our target is still valid
		if(this.target &&
			!this.target.parent)
		{
			this.target = null;
			this.stage = "target";
			this.isFleeing = false;
		}
		switch(this.stage)
		{
			case "target":
				this.target = this.sense();
				if(this.target)
					this.alert();
				else
					this.idle();
				break;
			case "combat":
				if(!this.isFleeing)
					this.isFleeing = this.wimpy();
				if(this.isFleeing)
					this.flee();
				else
				{
					if(!this.attack())
						this.follow();
				}
				break;
		}
	},
	/** Called when the controlled mobile is hit. */
	wasHitBy: function(source)
	{
		this.stage = "combat";
		this.target = source;
	}
}
</script>
<script type="text/javascript">
// From File src/Entity/Mobile/Player.js
/** The player's mobile, which has special serialization rules.
*/
function PlayerMobile()
{
	this.skills =
	{
		unarmed: 1,
		melee: 1,
		firearms: 1
	};
	this.skillGains =
	{
		unarmed: 0,
		melee: 0,
		firearms: 0
	};
	this.worldMapVisRange = 3;
	this.init();
}
PlayerMobile.prototype = new Mobile(
	"Eddy Vance",				// Name
	"@",						// Character
	Color.white,				// Foreground color
	null,						// Background color
	100,						// HP
	10,							// Speed
	{							// Controller description
		sense: "none",
		alert: "none",
		idle: "none",
		wimpy: "none",
		flee: "none",
		attack: "none",
		follow: "none"
	}
);
PlayerMobile.prototype.serialize = function(s)
{
	Mobile.prototype.serialize.call(this, s);
	s.push(this.name, this.skills, this.skillGains);
}
PlayerMobile.prototype.deserialize = function(s)
{
	this.skillGains = s.pop();
	this.skills = s.pop();
	this.name = s.pop();
	Mobile.prototype.deserialize.call(this, s);
}
/** Checks the palyer's skill against a set difficulty and handles skill gains.
 *@param skillName The name of the skill to check.
 *@param difficulty The difficulty level to check against.
 */
PlayerMobile.prototype.skillCheck = function(skillName, difficulty)
{
	var skill = this.skills[skillName];
	if(!skill)
		return false;
	var success = Math.random() * 10 < ((skill - difficulty) + 5);
	if(Math.random() * 10 > ((skill - difficulty) + 5))
	{
		++this.skillGains[skillName];
		if(this.skillGains[skillName] >= 20)
		{
			this.skillGains[skillName] = 0;
			++this.skills[skillName];
			logWindow.pushMessage("%b0You gained a level in " + skillName + " skill! [" + this.skills[skillName] + "]");
		}
	}
	return success;
}
Entity.registerCtor(PlayerMobile, 1);
</script>
<script type="text/javascript">
// From File src/Entity/Mobile/Rat.js
/** Rat mobile, used as environmental flavor.
 */
function RatMobile()
{
	this.skills =
	{
		unarmed: 1
	};
	this.init();
}
RatMobile.prototype = new Mobile(
	"rat",						// Name
	"r",						// Character
	Color.white,				// Foreground color
	null,						// Background color
	10,							// HP
	20,							// Speed
	{							// Controller description
		sense: "lookForPlayer",
		alert: "run",
		idle: "wander",
		wimpy: "run",
		flee: "run",
		attack: "none",
		follow: "none"
	}
);
Entity.registerCtor(RatMobile, 2);
</script>
<script type="text/javascript">
// From File src/Entity/Mobile/WildDog.js
/** Wild dog mobile, used as a basic enemy.
 */
function WildDogMobile()
{
	this.skills =
	{
		unarmed: 1
	};
	this.init();
}
WildDogMobile.prototype = new Mobile(
	"wild dog",					// Name
	"d",						// Character
	new Color(0.8, 0.8, 0.8),	// Foreground color
	null,						// Background color
	30,							// HP
	17,							// Speed
	{							// Controller description
		sense: "lookForPlayer",
		alert: "attack",
		idle: "wander",
		wimpy: "none",
		flee: "run",
		attack: "attack",
		follow: "follow"
	}
);
Entity.registerCtor(WildDogMobile, 5);
</script>
<script type="text/javascript">
// From File src/Entity/Mobile/Zombie.js
/** A random zombile mobile, yay!
 */
function ZombieMobile()
{
	this.seed = 0;
	this.level = 1;
	this.skills =
	{
		unarmed: 1,
		melee: 1,
		firearms: 1
	};
	this.init();
}
/** This is the data used by the _generate method to construct our zombies.
 */
ZombieMobile._zombieTypes =
[
	{
		name: "zombie child",
		skills:
		{
			unarmed: 0,
			melee: -1,
			firearms: -5
		}
	}
]
ZombieMobile.prototype = new Mobile(
	"zombie",					// Name
	"Z",						// Character
	Color.green,				// Foreground color
	null,						// Background color
	100,						// HP
	30,							// Speed
	{							// Controller description
		sense: "none",
		alert: "none",
		idle: "none",
		wimpy: "none",
		flee: "none",
		attack: "none",
		follow: "none"
	}
);
ZombieMobile.prototype.serialize = function(s)
{
	Mobile.prototype.serialize.call(this, s);
	s.push(this.seed, this.level);
}
ZombieMobile.prototype.deserialize = function(s)
{
	this.level = s.pop();
	this.seed = s.pop();
	Mobile.prototype.deserialize.call(this, s);
	this._generate();
}
/** Set the seed and level of the zombie, generating all of the lovely
 * variations.
 * @param seed The seed value used to generate this zombie.
 * @param level The desired level of the zomebie.
 */
ZombieMobile.prototype.setValues = function(seed, level)
{
}
/** Generate stats, skills and everything else for this zombie based on the seed
 * and level values.
 */
ZombieMobile.prototype._generate = function()
{
}
Entity.registerCtor(ZombieMobile, 3);
</script>
<script type="text/javascript">
// From File src/mode/TitleScreenMode.js
/** Title screen mode handler.
 */
function TitleScreenMode()
{
	this.buffer = new ScreenBuffer(host.screen.width, host.screen.height);
	this.generate();
	this.render();
}
TitleScreenMode.prototype =
{
	/** Generate the desert horizon background.
	 */
	generate: function()
	{
		var self = this;

		/** Cloud blob method, creates a noisy blob of alpha-blended white.
		 *@param x Center X.
		 *@param y Center Y.
		 */
		var cloudOfs = [0, 0, 0, -1, 0, 1, -1, 0, 1, 0];
		function cloudBlob(x, y)
		{
			var i, ix, iy, a, cell, c;
			for(i = 0; i < cloudOfs.length; i += 2)
			{
				ix = x + cloudOfs[i];
				iy = y + cloudOfs[i + 1];
				a = Math.random() * 0.25;
				a += 0.25;
				cell = self.buffer.getCell(ix, iy);
				if(cell == null)
					continue;
				c = cell.bg.alphaBlend(Color.white, a);
				self.buffer.setCell(ix, iy, null, null, c);
			}
		}

		// Generate the ground and sky
		var groundTopY = Math.floor(this.buffer.height / 2.25);
		var skyTop = new Color(0.16, 0.235, 0.51);
		var skyBottom = new Color(0.51, 0.835, 1);
		var groundTop = new Color(1, 0.85, 0);
		var groundBottom = new Color(0.66, 0.33, 0);
		var i = 0, j, c;
		while(i < groundTopY)
		{
			for(j = 0; j < this.buffer.width; ++j)
			{
				c = Color.interpolate(skyTop, skyBottom, i / (groundTopY - 1));
				c.r += Math.random() * 0.025 - 0.0125;
				c.g += Math.random() * 0.025 - 0.0125;
				c.b += Math.random() * 0.025 - 0.0125;
				this.buffer.setCell(j, i, " ", null, c);
			}
			++i;
		}
		while(i < this.buffer.height)
		{
			for(j = 0; j < this.buffer.width; ++j)
			{
				c = Color.interpolate(groundTop, groundBottom, (i - groundTopY) / (this.buffer.height - groundTopY));
				c.r += Math.random() * 0.05 - 0.025;
				c.g += Math.random() * 0.05 - 0.025;
				c.b += Math.random() * 0.05 - 0.025;
				this.buffer.setCell(j, i, " ", null, c);
			}
			++i;
		}

		// Generate clouds
		var cx, cy, height, length, count = Math.floor(Math.random() * 5) + 5;
		for(i = 0; i < count; ++i)
		{
			cx = Math.floor(Math.random() * this.buffer.width);
			cy = Math.floor(Math.random() * groundTopY) - 1;
			length = Math.floor(Math.random() * this.buffer.width / 3) + 4;
			for(j = 0; j < length; ++j)
			{
				cloudBlob(cx + j, cy);
			}
		}

		// Generate cacti
		var cactiTop = new Color(0.15, 1, 0.05);
		var cactiBottom = new Color(0, 0.75, 0.15);
		for(i = 0; i < 25; ++i)
		{
			if(i > 15)
			{
				cy = Math.floor(Math.random() * (this.buffer.height - groundTopY));
				height = Math.floor(cy / 4) + 3;
				cy += groundTopY - 1;
			}
			else
			{
				cy = Math.floor(Math.random() * (this.buffer.height - groundTopY * 1.75));
				height = Math.floor(cy / 4) + 3;
				cy += groundTopY - 1;
			}
			cx = Math.floor(Math.random() * this.buffer.width);
			for(j = 0; j < height; ++j)
			{
				this.buffer.setCell(cx, cy + j, ";", Color.white, Color.interpolate(cactiTop, cactiBottom, j / (height - 1)));
			}
		}
	},
	/** Create the main menu.
	 */
	createMainMenu: function()
	{
		var menuOptions = [];
		if(isSaveAvailable())
			menuOptions.push("Continue Game");
		menuOptions.push("New Game", "About");
		modeStack.push(
			new MenuListMode(
				host.screen,
				20,
				null,
				menuOptions,
				this.menuCallback,
				this,
				true,
				true
			)
		);
	},
	/** Menu callback.
	 */
	menuCallback: function(action, source)
	{
		switch(action)
		{
			case "Continue Game":
				if(loadGame())
				{
					source.close();
					removeMode(this);
				}
				break;
			case "New Game":
				if(newGame())
				{
					source.close();
					removeMode(this);
				}
				break;
			case "About":
				textBox("About");
				break;
		}
		return true;
	},
	/** Render the title image to the screen.
	 */
	render: function()
	{
		this.buffer.blitTo(host.screen, 0, 0, true);

		// Title, version and author overlay
		var lines = ["Shambletown Ver. " + version, "by", "Norman B. Lancaster (QBRADQ)"];
		var line, i, tx, ty = host.screen.height - (4 + lines.length);
		for(i = 0; i < lines.length; ++i)
		{
			line = lines[i];
			tx = Math.floor((host.screen.width - line.length) / 2);
			host.screen.blitString(tx, ty, "%4g" + line);
			++ty;
		}
	}
};
</script>
<script type="text/javascript">
// From File src/mode/LogWindow.js
/** Log window UI element.
@param buffer The ScreenBuffer object to render to.
@param x X offset.
@param y Y offset.
@param width Width.
@param height Height.
@param depth How many turns worth of messages to remember in the history buffer.
*/
function LogWindow(buffer, x, y, width, height, depth)
{
	this.buffer = buffer;
	this.x = x;
	this.y = y;
	this.width = width;
	this.height = height;
	this.history = [];
	for(var i = 0; i < depth; ++i)
	{
		this.history.push("");
	}
	this.historyIndex = 0;
	this.top = 0;
	this.lines = [];
	this.isTempMessage = false;
}
LogWindow.prototype =
{
	/** Handle actions for the log window. This is always called before any
	 *other handler when in game mode.
	 *@param action The action string to handle.
	 */
	handleAction: function(action)
	{
		var hidxChanged = false;
		switch(action)
		{
			case "logup": --this.top; break;
			case "logdown": ++this.top; break;
			case "historyup": ++this.historyIndex; hidxChanged = true; break;
			case "historydown": --this.historyIndex; hidxChanged = true; break;
			default:
				return false;
				break;
		}
		if(hidxChanged)
		{
			if(this.historyIndex < 0)
				this.historyIndex = 0;
			if(this.historyIndex >= this.history.length)
				this.historyIndex = this.history.length - 1;
			else
			{
				if(this.historyIndex == 1 &&
					this.lines.length == 0 )
					this.historyIndex = 0;
				var idx = this.historyIndex;
				this.clearMessage();
				this.historyIndex = idx;
				var lines = this.history[(this.history.length - 1) - this.historyIndex].split("\n");
				for(var i = 0; i < lines.length; ++i)
					this.pushMessage(lines[i], true);
				this.isTempMessage = true;
				this.historyIndex = idx;
			}
		}
		else
		{
			if(this.top + this.height > this.lines.length)
				this.top = this.lines.length - this.height;
			if(this.top < 0)
				this.top = 0;
		}
		this.render();
		return true;
	},
	/** Clears the current message and pushes it onto the history buffer.
	 */
	clearMessage: function()
	{
		if(!this.isTempMessage &&
			this.lines.length > 0 &&
			this.lines[0].length > 0)
		{
			this.history.push(this.lines.join("\n"));
			this.history.splice(0, 1);
		}
		this.historyIndex = 0;
		this.lines = [];
		this.top = 0;
		this.render();
	},
	/** Replace the current message with a temporary message.
	 *@param str The message to display.
	 */
	tempMessage: function(str)
	{
		this.isTempMessage = true;
		this.pushMessage(str);
		this.isTempMessage = true;
	},
	/** Push a message onto the log.
	@param str Message string.
	@param doNotRender If true skip rendering the log window.
	*/
	pushMessage: function(str, doNotRender)
	{
		if(this.isTempMessage)
		{
			this.clearMessage();
			this.isTempMessage = false;
		}
		if(str.length > 0)
		{
			var strLines = util.wordWrap(str, this.width - 1);
			for(var i = 0; i < strLines.length; ++i)
			{
				this.lines.push(strLines[i]);
			}
		}
		if(!doNotRender)
			this.render();
	},
	/** Returns true if the log is currently empty.
	 */
	isEmpty: function()
	{
		return this.lines.length == 0;
	},
	/** Redraws the log display on the root window.
	*/
	render: function()
	{
		var i, line;
		
		// Render lines
		for(i = 0; i < this.height; ++i)
		{
			if(i + this.top >= this.lines.length)
			{
				this.buffer.blitRect(this.x, this.y + i, this.width, 1, " ", null, Color.black);
				continue;
			}
			line = this.lines[i + this.top];
			this.buffer.blitString(this.x, this.y + i, line, this.width - 1);
		}
		
		// Render scroll indicators if needed
		if(this.top > 0 ||
			this.top + this.height < this.lines.length)
			this.buffer.blitRect(this.x + (this.width - 1), this.y, 1, this.height, "|", Color.black, Color.white);
		else
			this.buffer.blitRect(this.x + (this.width - 1), this.y, 1, this.height, " ", Color.black, Color.black);
		if(this.top > 0)
			this.buffer.setCell(this.x + (this.width - 1), this.y, "^", null, null);
		if(this.top + this.height < this.lines.length)
			this.buffer.setCell(this.x + (this.width - 1), this.y + (this.height - 1), "v", null, null);
	}
};
</script>
<script type="text/javascript">
// From File src/mode/WorldMapMode.js
/** Mode handler for the world map.
@param map The map to handle.
*/
function WorldMapMode(map)
{
	this._map = map;
	this.render();
}
WorldMapMode.prototype =
{
	/** Handle an input action.
	 *@param action The action string to handle.
	*/
	handleAction: function(action)
	{
		switch(action)
		{
			case "up": this._map.tryMovePlayer(0, -1); break;
			case "down": this._map.tryMovePlayer(0, 1); break;
			case "left": this._map.tryMovePlayer(-1, 0); break;
			case "right": this._map.tryMovePlayer(1, 0); break;
			case "help":
				modeStack.push(new TextReaderMode(host.screen, "This is some\ntext and other junk! Yay!\nGots to get a text resource compiler soon...\n\n\n\nStuff!"));
				return true;
				break;
			case "use":
				this._map.tryUse();
				return true;
				break;
			case "look":
				modeStack.push(
					new SelectPositionMode(
						this._map,
						this._map.player.x,
						this._map.player.y,
						0,
						this._lookCallback,
						this
					)
				);
				return true;
				break;
			case "menu":
				modeStack.push(
					new MenuListMode(
						host.screen,
						20,
						"Main Menu",
						["Return to Game", "Save and Quit", "Quit Game"],
						this._menuCallback,
						this
					)
				);
				return true;
				break;
			default: return false; break;
		}
		this._updateStatus();
		return true;
	},
	/** Menu callback.
	*/
	_menuCallback: function(option)
	{
		switch(option)
		{
			case "Save and Quit":
				if(saveGame())
				{
					leaveGameMode();
					enterTitleMode();
				}
				break;
			case "Quit Game":
				modeStack.push(
					new MenuListMode(
						host.screen,
						30,
						"Quit this game and delete your save file?",
						["No", "Yes"],
						this._quitConfirmCallback,
						this
					)
				);
				break;
		}
	},
	/** Quit confirm callback.
	 */
	_quitConfirmCallback: function(option)
	{
		if(option == "Yes")
		{
			deleteSave();
			leaveGameMode();
			enterTitleMode();
		}
	},
	/** Look command callback.
	*/
	_lookCallback: function(x, y, canceled)
	{
		this._updateStatus();
		if(canceled)
			return;
		logWindow.pushMessage("You see " + this._map.getPositionDescription(x, y));
	},
	/** Rerender everything.
	*/
	render: function()
	{
		this._map.render();
	},
	/** Update the status bar.
	*/
	_updateStatus: function()
	{
		var psec = this._map.getPlayerSector();
		if(psec)
		{
			var status = psec.name;
			// status += " (" + (Math.floor(psec.heat * 1000) / 10) + "F)";
			//status += " [" + psec.index + "]";
			logWindow.tempMessage("%f0" + status);
		}
	}
};
</script>
<script type="text/javascript">
// From File src/mode/SectorMapMode.js
/** Mode handler for a sector map.
@param map The sector map to use.
*/
function SectorMapMode(map)
{
	this._map = map;
	this._map.generate();
	this.render();
	logWindow.pushMessage("%f0Entering " + map.name);
}
SectorMapMode.prototype =
{
	/** Handle an input action..
	 *@param action The action string to handle.
	 */
	handleAction: function(action)
	{
		switch(action)
		{
			case "up": this._doMove(0, -1); break;
			case "down": this._doMove(0, 1); break;
			case "left": this._doMove(-1, 0); break;
			case "right": this._doMove(1, 0); break;
			case "look":
				modeStack.push(
					new SelectPositionMode(
						this._map,
						player.x,
						player.y,
						0,
						this._lookCallback,
						this
					)
				);
				break;
			default: return true; break;
		}
		this._doStatusLine();
		return true;
	},
	/** Look command callback
	*/
	_lookCallback: function(x, y, canceled)
	{
		if(canceled)
			return;
		logWindow.pushMessage("You see " + this._map.getPositionDescription(x, y));
	},
	/** Move the player.
	@param xOfs X offset.
	@param yOfs Y offset.
	*/
	_doMove: function(xOfs, yOfs)
	{
		var nx = player.x + xOfs;
		var ny = player.y + yOfs;

		// Handle the player leaving the map
		if(nx < 0 ||
			ny < 0 ||
			nx >= this._map.width ||
			ny >= this._map.height)
		{
			this._map.removeEntity(player);
			worldMap.onPlayerReenter(player);
			removeMode(this);
			modeStack.push(worldMapMode);
			worldMapMode.render();
			return;
		}
		// Normal move
		if(this._map.tryMoveEntity(player, nx, ny))
		{
			logWindow.clearMessage();
			this._map.doTurn();
		}
		// Bumped into something
		else
		{
			var ents = this._map.getEntitiesAtPosition(nx, ny);
			for(var i = ents.length - 1; i >= 0; --i)
			{
				if(ents[i].blockWalk)
				{
					logWindow.clearMessage();
					var ent = ents[i];
					// If it's a mobile attack it
					if(ent.isMobile)
						player.attack(ent);
					// Otherwise try to use it
					else
						ents[i].use(player);
					this._map.doTurn();
					break;
				}
			}
		}
	},
	/** Create the "stuff under me" message if appropriote.
	 */
	_doStatusLine: function()
	{
		if(!logWindow.isEmpty())
			return;
		var ents = this._map.getEntitiesAtPosition(player.x, player.y);
		var desc = "";
		for(var i = 0; i < ents.length; ++i)
		{
			var ent = ents[i];
			if(ent == player)
				continue;
			if(desc.length > 0)
				desc += ", ";
			desc += ent.article + " " + ent.name;
		}
		if(desc.length > 0)
			logWindow.tempMessage("%f0" + desc);
	},
	/** Rerender everything.
	*/
	render: function()
	{
		this._map.render();
	}
};
</script>
<script type="text/javascript">
// From File src/mode/SelectPositionMode.js
/** Mode handler for selecting a map location.
@param map Map object (supporting renderPosition, renderOverlay and getPositionDescription methods).
@param x Center of selection X.
@param y Center of selection Y.
@param r Selection radius, use 0 for anywhere.
@param callback Callback method to invoke when the selection is complete. Callback parameters are (x, y, canceled).
@param context The context of the callback method.
*/
function SelectPositionMode(map, x, y, r, callback, context)
{
	this.map = map;
	this.cursorX = x;
	this.cursorY = y;
	this.startX = x;
	this.startY = y;
	this.radius = r;
	this.callback = callback;
	this.callbackContext = context;
	this.render();
}
SelectPositionMode.prototype =
{
	/** Handle an input action.
	*/
	handleAction: function(action)
	{
		switch(action)
		{
			case "up": this._tryMoveCursor(0, -1); break;
			case "down": this._tryMoveCursor(0, 1); break;
			case "left": this._tryMoveCursor(-1, 0); break;
			case "right": this._tryMoveCursor(1, 0); break;
			case "use":
				this.unrender();
				this.callback.call(this.callbackContext, this.cursorX, this.cursorY, false);
				removeMode(this);
				break;
			case "cancel":
				this.unrender();
				this.callback.call(this.callbackContext, this.cursorX, this.cursorY, true);
				removeMode(this);
				break;
			default: return true; break;
		}
		return true;
	},
	/** Try to move the cursor.
	@param xOfs X offset.
	@param yOfs Y offset.
	*/
	_tryMoveCursor: function(xOfs, yOfs)
	{
		var nx = this.cursorX + xOfs;
		var ny = this.cursorY + yOfs;
		if(nx < 0 ||
			ny < 0 ||
			nx >= this.map.width ||
			ny >= this.map.height)
			return;
		
		if(this.radius > 0)
		{
			var sd = Math.abs(this.startX - nx) + Math.abs(this.startY - ny);
			if(sd > this.radius)
				return;
		}
		
		this.unrender();
		this.cursorX = nx;
		this.cursorY = ny;
		this.render();
	},
	/** Rerender the cursor.
	*/
	render: function()
	{
		logWindow.tempMessage("%f0" +this.map.getPositionDescription(this.cursorX, this.cursorY));
		this.map.renderOverlay(this.cursorX, this.cursorY - 1, "v", Color.white, null);
		this.map.renderOverlay(this.cursorX, this.cursorY + 1, "^", Color.white, null);
		this.map.renderOverlay(this.cursorX - 1, this.cursorY, ">", Color.white, null);
		this.map.renderOverlay(this.cursorX + 1, this.cursorY, "<", Color.white, null);
	},
	/** Unrender the cursor
	*/
	unrender: function()
	{
		this.map.renderPosition(this.cursorX, this.cursorY - 1);
		this.map.renderPosition(this.cursorX, this.cursorY + 1);
		this.map.renderPosition(this.cursorX - 1, this.cursorY);
		this.map.renderPosition(this.cursorX + 1, this.cursorY);
	}
};
</script>
<script type="text/javascript">
// From File src/mode/TextReaderMode.js
/** Text document reader mode, used for help files and story text.
@param buffer Screen buffer to display to.
@param text Text to display.
@param callback Callback method to be called when the text reader is closed.
@param context Context of the callback method.
*/
function TextReaderMode(buffer, text, callback, context)
{
	this.buffer = buffer;
	this.lines = text.split("\n");
	this.height = this.lines.length;
	if(this.height > this.buffer.height - 8)
		this.height = this.buffer.height - 8;
	this.width = 50;
	this.callback = callback;
	this.callbackContext = context;
	this.top = 0;
	this.render();
}
TextReaderMode.prototype =
{
	/** Handle an input action.
	 *@param action The action string to handle.
	*/
	handleAction: function(action)
	{
		switch(action)
		{
			case "up": --this.top; break;
			case "down": ++this.top; break;
			case "left": this.top -= this.height - 2; break;
			case "right": this.top += this.height - 2; break;
			case "use":
			case "cancel":
				removeMode(this);
				if(this.callback)
					this.callback.call(this.callbackContext);
				return true;
				break;
			default: return true; break;
		}
		if(this.top < 0)
			this.top = 0;
		if(this.top + this.height > this.lines.length)
			this.top = this.lines.length - this.height;
		this.render();
		return true;
	},
	/** Rerender the text.
	*/
	render: function()
	{
		var iLine = this.top;
		var width = this.width;
		if(this.height < this.lines.length)
			width += 1;
		var left = Math.floor((this.buffer.width - width) / 2);
		var top = Math.floor((this.buffer.height - this.height) / 2);
		this.buffer.drawMenuBox(left, top, width, this.height);
		if(this.height < this.lines.length)
		{
			this.buffer.blitRect(left + this.width, top, 1, this.height, " ", null, Color.darkGray);
			var pos = Math.floor((this.top / (this.lines.length - this.height)) * this.height);
			pos = Math.min(pos, this.height - 1);
			this.buffer.setCell(left + this.width, top + pos, " ", null, Color.gray);
		}
		for(var i = 0; i < this.height; ++i)
		{
			if(iLine < this.lines.length)
			{
				this.buffer.blitString(left, top + i, this.lines[iLine], this.width);
				++iLine;
			}
			else
				this.buffer.blitString(left, top + i, "", this.width);
		}
	}
}
</script>
<script type="text/javascript">
// From File src/mode/MenuListMode.js
/** Menu list mode handler.
@param buffer Buffer to render to.
@param width Width of the menu.
@param text Text to display above the menu area.
@param options An array of strings to select from. If this is null or an empty array, callback will be called with a null string after confirm or exit are pressed.
@param callback A callback method whos parameters are: option, the string that was selected (or null if no string was selected), source, the MenuListMode object that generated the callback. (Optional)
@param context The context of the callback. (Must be specified if callback is not null).
@param notCancelable If true this user will not be allowed to cancel out of the menu.
@param doNotClose If true this menu will not close before the callback is called. Use the close method to close the menu.
*/
function MenuListMode(buffer, width, text, options, callback, context, notCancelable, doNotClose)
{
	this.buffer = buffer;
	this.width = width;
	if(text == null ||
		text.length <= 0)
		this.lines = [];
	else
		this.lines = util.wordWrap(text, width);
	this.options = options;
	if(this.options == null)
		this.options = [];
	this.height = this.options.length + this.lines.length;
	if(this.lines.length > 0 &&
		this.options.length > 0)
		this.height += 1;
	this.callback = callback;
	this.callbackContext = context;
	this.notCancelable = notCancelable ? true : false;
	this.doNotClose = doNotClose ? true : false;
	this.selectedIndex = 0;
	this.render();
}
MenuListMode.prototype =
{
	/** Handle an input action.
	 *@param action The action string to handle.
	*/
	handleAction: function(action)
	{
		switch(action)
		{
			case "up":--this.selectedIndex;break;
			case "down":++this.selectedIndex;break;
			case "use":
				var option;
				if(this.selectedIndex < 0 ||
					this.selectedIndex > this.options.length)
					option = null;
				else
					option = this.options[this.selectedIndex];
				if(this.callback)
					this.callback.call(this.callbackContext, option, this);
				if(!this.doNotClose)
					this.close();
				return true;
				break;
			case "cancel":
				if(this.notCancelable)
					return true;
				if(this.callback)
					this.callback.call(this.callbackContext, null, this);
				if(!this.doNotClose)
					this.close();
				return true;
				break;
			default:return true;break;
		}
		if(this.selectedIndex < 0)
			this.selectedIndex = this.options.length - 1;
		if(this.selectedIndex >= this.options.length)
			this.selectedIndex = 0;
		this.render();
		return true;
	},
	/** Close this menu.
	 */
	close: function()
	{
		removeMode(this);
	},
	/** Render the menu.
	*/
	render: function()
	{
		var i;

		// Positioning and menu box
		var left = Math.floor((this.buffer.width - (this.width + 2)) / 2);
		var top = Math.floor((this.buffer.height - (this.height + 2)) / 2);
		this.buffer.drawMenuBox(left, top, this.width + 2, this.height + 2);
		this.buffer.drawBox(left, top, this.width + 2, this.height + 2, " ", null, Color.black);

		// Text
		var yOfs = top + 1;
		this.buffer.blitString(0, 0, "%f0");
		for(i = 0; i < this.lines.length; ++i)
		{
			this.buffer.blitString(left + 1, yOfs++, this.lines[i], this.width);
		}
		if(this.lines.length > 0 &&
			this.options.length > 0)
		{
			this.buffer.blitRect(left, yOfs++, this.width + 2, 1, " ", null, Color.black);
		}

		// Options
		var optionsTop = yOfs;
		for(i = 0; i < this.options.length; ++i)
		{
			var option = this.options[i];
			var leftOfs = Math.floor((this.width - option.length) / 2);
			if(leftOfs < 0)
				leftOfs = 0;
			this.buffer.blitRect(left + 1, yOfs, leftOfs, 1, " ", null, Color.black);
			this.buffer.blitString(left + 1 + leftOfs, yOfs++, "%f0" + option, this.width - leftOfs);
		}
		
		// Highlight selected option
		if(this.options.length > 0)
			this.buffer.blitRect(left, optionsTop + this.selectedIndex, this.width + 2, 1, null, null, Color.darkBlue);
	}
}
</script>
</head>

<body onload="main()"
	style="padding: 0; margin: 0;">
	<p>Loading...</p>
</body>

</html>
